diff --git a/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp b/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp index df3ff97a..ea1c364d 100644 --- a/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp +++ b/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp @@ -566,4 +566,20 @@ MMKV_EXPORT bool removeStorage(const char *mmapID, const char *rootPath) { return MMKV::removeStorage(mmapID, nullptr); } +MMKV_EXPORT bool isMultiProcess(void *handle) { + MMKV *kv = static_cast(handle); + if (kv) { + return kv->isMultiProcess(); + } + return false; +} + +MMKV_EXPORT bool isReadOnly(void *handle) { + MMKV *kv = static_cast(handle); + if (kv) { + return kv->isReadOnly(); + } + return false; +} + #endif // MMKV_DISABLE_FLUTTER diff --git a/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp b/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp index a1b5ddae..cd47561b 100644 --- a/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp +++ b/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp @@ -1064,6 +1064,22 @@ MMKV_JNI void clearAllWithKeepingSpace(JNIEnv *env, jobject instance) { } } +MMKV_JNI jboolean isMultiProcess(JNIEnv *env, jobject instance) { + MMKV *kv = getMMKV(env, instance); + if (kv) { + return (jboolean) kv->isMultiProcess(); + } + return jboolean(false); +} + +MMKV_JNI jboolean isReadOnly(JNIEnv *env, jobject instance) { + MMKV *kv = getMMKV(env, instance); + if (kv) { + return (jboolean) kv->isReadOnly(); + } + return jboolean(false); +} + } // namespace mmkv static JNINativeMethod g_methods[] = { @@ -1146,6 +1162,8 @@ static JNINativeMethod g_methods[] = { {"isEncryptionEnabled", "()Z", (void *) mmkv::isEncryptionEnabled}, {"isExpirationEnabled", "()Z", (void *) mmkv::isExpirationEnabled}, {"clearAllWithKeepingSpace", "()V", (void *) mmkv::clearAllWithKeepingSpace}, + {"isMultiProcess", "()Z", (void *) mmkv::isMultiProcess}, + {"isReadOnly", "()Z", (void *) mmkv::isReadOnly}, }; static int registerNativeMethods(JNIEnv *env, jclass cls) { 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 1c8a2274..14b85e93 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 @@ -334,6 +334,11 @@ public static void setLogLevel(MMKVLogLevel level) { static private final int BACKUP_MODE = 1 << 4; + /** + * Read-only mode. + */ + static public final int READ_ONLY_MODE = 1 << 5; + /** * Create an MMKV instance with an unique ID (in single-process mode). * @@ -1646,6 +1651,16 @@ private static void onContentChangedByOuterProcess(String mmapID) { */ public native void checkContentChangedByOuterProcess(); + /** + * Check if this instance is in multi-process mode. + */ + public native boolean isMultiProcess(); + + /** + * Check if this instance is in read-only mode. + */ + public native boolean isReadOnly(); + // jni private final long nativeHandle; diff --git a/Android/MMKV/mmkvdemo/build.gradle b/Android/MMKV/mmkvdemo/build.gradle index 61521fe0..012b992c 100644 --- a/Android/MMKV/mmkvdemo/build.gradle +++ b/Android/MMKV/mmkvdemo/build.gradle @@ -77,9 +77,9 @@ repositories { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation project(':mmkv') -// implementation 'com.tencent:mmkv:1.3.9' -// implementation 'com.tencent:mmkv-static:1.3.9' // this is identical to 'com.tencent:mmkv' -// implementation 'com.tencent:mmkv-shared:1.3.9' +// implementation 'com.tencent:mmkv:2.0.0' +// implementation 'com.tencent:mmkv-static:2.0.0' // this is identical to 'com.tencent:mmkv' +// implementation 'com.tencent:mmkv-shared:2.0.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.13.2' diff --git a/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MainActivity.java b/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MainActivity.java index 736e2f23..859ab889 100644 --- a/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MainActivity.java +++ b/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MainActivity.java @@ -131,6 +131,7 @@ public void onClick(View v) { // testFastNativeSpeed(); testRemoveStorage(); overrideTest(); + testReadOnly(); } private void testCompareBeforeSet() { @@ -212,7 +213,12 @@ private void testInterProcessLogic() { private MMKV testMMKV(String mmapID, String cryptKey, boolean decodeOnly, String rootPath) { //MMKV kv = MMKV.defaultMMKV(); MMKV kv = MMKV.mmkvWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, cryptKey, rootPath); + testMMKV(kv, decodeOnly); + Log.i("MMKV", "isFileValid[" + kv.mmapID() + "]: " + MMKV.isFileValid(kv.mmapID(), rootPath)); + return kv; + } + private void testMMKV(MMKV kv, boolean decodeOnly) { if (!decodeOnly) { kv.encode("bool", true); } @@ -291,9 +297,6 @@ private MMKV testMMKV(String mmapID, String cryptKey, boolean decodeOnly, String //kv.clearAll(); kv.clearMemoryCache(); Log.i("MMKV", "allKeys: " + Arrays.toString(kv.allKeys())); - Log.i("MMKV", "isFileValid[" + kv.mmapID() + "]: " + MMKV.isFileValid(kv.mmapID(), rootPath)); - - return kv; } private void testImportSharedPreferences() { @@ -915,4 +918,29 @@ private void encryptionTestKV(String key, String value) { } mmkv0.removeValueForKey(key); } + + private void testReadOnly() { + final String name = "testReadOnly"; + final String key = "readonly+key"; + { + MMKV kv = MMKV.mmkvWithID(name, MMKV.SINGLE_PROCESS_MODE, key); + testMMKV(kv, false); + kv.close(); + } + + String path = MMKV.getRootDir() + "/" + name; + File file = new File(path); + file.setReadOnly(); + File crcFile = new File(path + ".crc"); + crcFile.setReadOnly(); + + MMKV kv = MMKV.mmkvWithID(name, MMKV.SINGLE_PROCESS_MODE | MMKV.READ_ONLY_MODE, key); + testMMKV(kv, true); + + // also check if it tolerate update operations without crash + testMMKV(kv, false); + + file.setWritable(true); + crcFile.setWritable(true); + } } diff --git a/Core/MMKV.cpp b/Core/MMKV.cpp index 1f1d73d9..443a43b3 100644 --- a/Core/MMKV.cpp +++ b/Core/MMKV.cpp @@ -80,20 +80,21 @@ MMKVPath_t filename(const MMKVPath_t &path); #ifndef MMKV_ANDROID MMKV::MMKV(const string &mmapID, MMKVMode mode, string *cryptKey, MMKVPath_t *rootPath, size_t expectedCapacity) : m_mmapID(mmapID) + , m_mode(mode) , m_path(mappedKVPathWithID(m_mmapID, mode, rootPath)) , m_crcPath(crcPathWithID(m_mmapID, mode, rootPath)) , m_dic(nullptr) , m_dicCrypt(nullptr) , m_expectedCapacity(std::max(DEFAULT_MMAP_SIZE, roundUp(expectedCapacity, DEFAULT_MMAP_SIZE))) - , m_file(new MemoryFile(m_path, m_expectedCapacity)) - , m_metaFile(new MemoryFile(m_crcPath)) + , m_file(new MemoryFile(m_path, m_expectedCapacity, isReadOnly())) + , m_metaFile(new MemoryFile(m_crcPath, 0, isReadOnly())) , m_metaInfo(new MMKVMetaInfo()) , m_crypter(nullptr) , m_lock(new ThreadLock()) , m_fileLock(new FileLock(m_metaFile->getFd())) , m_sharedProcessLock(new InterProcessLock(m_fileLock, SharedLockType)) , m_exclusiveProcessLock(new InterProcessLock(m_fileLock, ExclusiveLockType)) - , m_isInterProcess((mode & MMKV_MULTI_PROCESS) != 0) { +{ m_actualSize = 0; m_output = nullptr; @@ -114,8 +115,8 @@ MMKV::MMKV(const string &mmapID, MMKVMode mode, string *cryptKey, MMKVPath_t *ro m_crcDigest = 0; m_lock->initialize(); - m_sharedProcessLock->m_enable = m_isInterProcess; - m_exclusiveProcessLock->m_enable = m_isInterProcess; + m_sharedProcessLock->m_enable = isMultiProcess(); + m_exclusiveProcessLock->m_enable = isMultiProcess(); // sensitive zone /*{ @@ -1094,15 +1095,19 @@ size_t MMKV::actualSize() { return m_actualSize; } -void MMKV::removeValueForKey(MMKVKey_t key) { +bool MMKV::removeValueForKey(MMKVKey_t key) { if (isKeyEmpty(key)) { - return; + return false; + } + if (isReadOnly()) { + MMKVWarning("[%s] file readonly", m_mmapID.c_str()); + return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); - removeDataForKey(key); + return removeDataForKey(key); } #ifndef MMKV_APPLE @@ -1129,9 +1134,13 @@ vector MMKV::allKeys(bool filterExpire) { return keys; } -void MMKV::removeValuesForKeys(const vector &arrKeys) { +bool MMKV::removeValuesForKeys(const vector &arrKeys) { + if (isReadOnly()) { + MMKVWarning("[%s] file readonly", m_mmapID.c_str()); + return false; + } if (arrKeys.empty()) { - return; + return true; } if (arrKeys.size() == 1) { return removeValueForKey(arrKeys[0]); @@ -1162,8 +1171,9 @@ void MMKV::removeValuesForKeys(const vector &arrKeys) { if (deleteCount > 0) { m_hasFullWriteback = false; - fullWriteback(); + return fullWriteback(); } + return true; } #endif // MMKV_APPLE @@ -1447,7 +1457,7 @@ bool MMKV::restoreOneFromDirectory(const string &mmapKey, const MMKVPath_t &srcP // reload data after restore kv->clearMemoryCache(); kv->loadFromFile(); - if (kv->m_isInterProcess) { + if (kv->isMultiProcess()) { kv->notifyContentChanged(); } diff --git a/Core/MMKV.h b/Core/MMKV.h index 20683da6..c031fec8 100644 --- a/Core/MMKV.h +++ b/Core/MMKV.h @@ -58,8 +58,13 @@ enum MMKVMode : uint32_t { MMKV_ASHMEM = 1 << 3, MMKV_BACKUP = 1 << 4, #endif + MMKV_READ_ONLY = 1 << 5, }; +static inline MMKVMode operator | (MMKVMode one, MMKVMode other) { + return static_cast(static_cast(one) | static_cast(other)); +} + #define MMKV_OUT #ifdef MMKV_HAS_CPP20 @@ -105,6 +110,7 @@ class MMKV { ~MMKV(); std::string m_mmapID; + const MMKVMode m_mode; MMKVPath_t m_path; MMKVPath_t m_crcPath; mmkv::MMKVMap *m_dic; @@ -281,8 +287,16 @@ class MMKV { static void onExit(); const std::string &mmapID() const; - - const bool m_isInterProcess; +#ifndef MMKV_ANDROID + bool isMultiProcess() const { return (m_mode & MMKV_MULTI_PROCESS) != 0; } +#else + bool isMultiProcess() const { + return (m_mode & MMKV_MULTI_PROCESS) != 0 + || (m_mode & CONTEXT_MODE_MULTI_PROCESS) != 0 + || (m_mode & MMKV_ASHMEM) != 0; // ashmem is always multi-process + } +#endif + bool isReadOnly() const { return (m_mode & MMKV_READ_ONLY) != 0; } #ifndef MMKV_DISABLE_CRYPT std::string cryptKey() const; @@ -428,7 +442,7 @@ class MMKV { // filterExpire: return all non-expired keys, keep in mind it comes with cost NSArray *allKeys(bool filterExpire = false); - void removeValuesForKeys(NSArray *arrKeys); + bool removeValuesForKeys(NSArray *arrKeys); typedef void (^EnumerateBlock)(NSString *key, BOOL *stop); void enumerateKeys(EnumerateBlock block); @@ -441,10 +455,10 @@ class MMKV { // filterExpire: return all non-expired keys, keep in mind it comes with cost std::vector allKeys(bool filterExpire = false); - void removeValuesForKeys(const std::vector &arrKeys); + bool removeValuesForKeys(const std::vector &arrKeys); #endif // MMKV_APPLE - void removeValueForKey(MMKVKey_t key); + bool removeValueForKey(MMKVKey_t key); // keepSpace: remove all keys but keep the file size not changed, running faster void clearAll(bool keepSpace = false); diff --git a/Core/MMKV_Android.cpp b/Core/MMKV_Android.cpp index c3cd94f6..2d213d72 100644 --- a/Core/MMKV_Android.cpp +++ b/Core/MMKV_Android.cpp @@ -41,20 +41,20 @@ extern ThreadLock *g_instanceLock; MMKV::MMKV(const string &mmapID, int size, MMKVMode mode, string *cryptKey, string *rootPath, size_t expectedCapacity) : m_mmapID((mode & MMKV_BACKUP) ? mmapID : mmapedKVKey(mmapID, rootPath)) // historically Android mistakenly use mmapKey as mmapID + , m_mode(mode) , m_path(mappedKVPathWithID(m_mmapID, mode, rootPath)) , m_crcPath(crcPathWithID(m_mmapID, mode, rootPath)) , m_dic(nullptr) , m_dicCrypt(nullptr) , m_expectedCapacity(std::max(DEFAULT_MMAP_SIZE, roundUp(expectedCapacity, DEFAULT_MMAP_SIZE))) - , m_file(new MemoryFile(m_path, size, (mode & MMKV_ASHMEM) ? MMFILE_TYPE_ASHMEM : MMFILE_TYPE_FILE, m_expectedCapacity)) - , m_metaFile(new MemoryFile(m_crcPath, DEFAULT_MMAP_SIZE, m_file->m_fileType)) + , m_file(new MemoryFile(m_path, size, (mode & MMKV_ASHMEM) ? MMFILE_TYPE_ASHMEM : MMFILE_TYPE_FILE, m_expectedCapacity, isReadOnly())) + , m_metaFile(new MemoryFile(m_crcPath, DEFAULT_MMAP_SIZE, m_file->m_fileType, 0, isReadOnly())) , m_metaInfo(new MMKVMetaInfo()) , m_crypter(nullptr) , m_lock(new ThreadLock()) , m_fileLock(new FileLock(m_metaFile->getFd(), (mode & MMKV_ASHMEM))) , m_sharedProcessLock(new InterProcessLock(m_fileLock, SharedLockType)) - , m_exclusiveProcessLock(new InterProcessLock(m_fileLock, ExclusiveLockType)) - , m_isInterProcess((mode & MMKV_MULTI_PROCESS) != 0 || (mode & CONTEXT_MODE_MULTI_PROCESS) != 0) { + , m_exclusiveProcessLock(new InterProcessLock(m_fileLock, ExclusiveLockType)) { m_actualSize = 0; m_output = nullptr; @@ -78,8 +78,8 @@ MMKV::MMKV(const string &mmapID, int size, MMKVMode mode, string *cryptKey, stri m_crcDigest = 0; - m_sharedProcessLock->m_enable = m_isInterProcess; - m_exclusiveProcessLock->m_enable = m_isInterProcess; + m_sharedProcessLock->m_enable = isMultiProcess(); + m_exclusiveProcessLock->m_enable = isMultiProcess(); // sensitive zone /*{ @@ -90,6 +90,7 @@ MMKV::MMKV(const string &mmapID, int size, MMKVMode mode, string *cryptKey, stri MMKV::MMKV(const string &mmapID, int ashmemFD, int ashmemMetaFD, string *cryptKey) : m_mmapID(mmapID) + , m_mode(MMKV_ASHMEM) , m_path(mappedKVPathWithID(m_mmapID, MMKV_ASHMEM, nullptr)) , m_crcPath(crcPathWithID(m_mmapID, MMKV_ASHMEM, nullptr)) , m_dic(nullptr) @@ -101,8 +102,7 @@ MMKV::MMKV(const string &mmapID, int ashmemFD, int ashmemMetaFD, string *cryptKe , m_lock(new ThreadLock()) , m_fileLock(new FileLock(m_metaFile->getFd(), true)) , m_sharedProcessLock(new InterProcessLock(m_fileLock, SharedLockType)) - , m_exclusiveProcessLock(new InterProcessLock(m_fileLock, ExclusiveLockType)) - , m_isInterProcess(true) { + , m_exclusiveProcessLock(new InterProcessLock(m_fileLock, ExclusiveLockType)) { m_actualSize = 0; m_output = nullptr; @@ -127,8 +127,8 @@ MMKV::MMKV(const string &mmapID, int ashmemFD, int ashmemMetaFD, string *cryptKe m_crcDigest = 0; - m_sharedProcessLock->m_enable = m_isInterProcess; - m_exclusiveProcessLock->m_enable = m_isInterProcess; + m_sharedProcessLock->m_enable = true; + m_exclusiveProcessLock->m_enable = true; // sensitive zone /*{ @@ -213,7 +213,7 @@ bool MMKV::checkProcessMode() { return true; } - if (m_isInterProcess) { + if (isMultiProcess()) { if (!m_exclusiveProcessModeLock) { m_exclusiveProcessModeLock = new InterProcessLock(m_fileModeLock, ExclusiveLockType); } diff --git a/Core/MMKV_IO.cpp b/Core/MMKV_IO.cpp index dd43c533..1d005b4b 100755 --- a/Core/MMKV_IO.cpp +++ b/Core/MMKV_IO.cpp @@ -81,7 +81,7 @@ void MMKV::loadFromFile() { checkDataValid(loadFromFile, needFullWriteback); MMKVInfo("loading [%s] with %zu actual size, file size %zu, InterProcess %d, meta info " "version:%u", - m_mmapID.c_str(), m_actualSize, m_file->getFileSize(), m_isInterProcess, m_metaInfo->m_version); + m_mmapID.c_str(), m_actualSize, m_file->getFileSize(), isMultiProcess(), m_metaInfo->m_version); auto ptr = (uint8_t *) m_file->getMemory(); // loading if (loadFromFile && m_actualSize > 0) { @@ -303,7 +303,7 @@ void MMKV::checkLoadData() { loadFromFile(); return; } - if (!m_isInterProcess) { + if (!isMultiProcess()) { return; } @@ -397,6 +397,10 @@ bool MMKV::ensureMemorySize(size_t newSize) { MMKVWarning("[%s] file not valid", m_mmapID.c_str()); return false; } + if (isReadOnly()) { + MMKVWarning("[%s] file readonly", m_mmapID.c_str()); + return false; + } if (newSize >= m_output->spaceLeft() || (m_crypter ? m_dicCrypt->empty() : m_dic->empty())) { // remove expired keys @@ -590,7 +594,7 @@ bool MMKV::setDataForKey(MMBuffer &&data, MMKVKey_t key, bool isDataHolder) { } auto itr = m_dicCrypt->find(key); if (itr != m_dicCrypt->end()) { - bool onlyOneKey = !m_isInterProcess && m_dicCrypt->size() == 1; + bool onlyOneKey = !isMultiProcess() && m_dicCrypt->size() == 1; # ifdef MMKV_APPLE KVHolderRet_t ret; if (onlyOneKey) { @@ -629,7 +633,7 @@ bool MMKV::setDataForKey(MMBuffer &&data, MMKVKey_t key, bool isDataHolder) { } } } else { - bool needOverride = !m_isInterProcess && m_dicCrypt->empty() && m_actualSize > 0; + bool needOverride = !isMultiProcess() && m_dicCrypt->empty() && m_actualSize > 0; KVHolderRet_t ret; if (needOverride) { ret = overrideDataWithKey(data, key, isDataHolder); @@ -681,7 +685,7 @@ bool MMKV::setDataForKey(MMBuffer &&data, MMKVKey_t key, bool isDataHolder) { } } - bool onlyOneKey = !m_isInterProcess && m_dic->size() == 1; + bool onlyOneKey = !isMultiProcess() && m_dic->size() == 1; if (mmkv_likely(!m_enableKeyExpire)) { KVHolderRet_t ret; if (onlyOneKey) { @@ -713,7 +717,7 @@ bool MMKV::setDataForKey(MMBuffer &&data, MMKVKey_t key, bool isDataHolder) { } } } else { - bool needOverride = !m_isInterProcess && m_dic->empty() && m_actualSize > 0; + bool needOverride = !isMultiProcess() && m_dic->empty() && m_actualSize > 0; KVHolderRet_t ret; if (needOverride) { ret = overrideDataWithKey(data, key, isDataHolder); @@ -1338,6 +1342,10 @@ bool MMKV::reKey(const string &cryptKey) { MMKVWarning("[%s] file not valid", m_mmapID.c_str()); return false; } + if (isReadOnly()) { + MMKVWarning("[%s] file readonly", m_mmapID.c_str()); + return false; + } bool ret = false; if (m_crypter) { @@ -1408,6 +1416,10 @@ void MMKV::trim() { MMKVWarning("[%s] file not valid", m_mmapID.c_str()); return; } + if (isReadOnly()) { + MMKVWarning("[%s] file readonly", m_mmapID.c_str()); + return; + } if (m_actualSize == 0) { clearAll(); @@ -1452,6 +1464,10 @@ void MMKV::clearAll(bool keepSpace) { MMKVWarning("[%s] file not valid", m_mmapID.c_str()); return; } + if (isReadOnly()) { + MMKVWarning("[%s] file readonly", m_mmapID.c_str()); + return; + } if (m_file->getFileSize() == m_expectedCapacity && m_actualSize == 0) { MMKVInfo("nothing to clear for [%s]", m_mmapID.c_str()); @@ -1625,6 +1641,10 @@ bool MMKV::enableAutoKeyExpire(uint32_t expiredInSeconds) { if (m_metaInfo->hasFlag(MMKVMetaInfo::EnableKeyExipre)) { return true; } + if (isReadOnly()) { + MMKVWarning("[%s] file readonly", m_mmapID.c_str()); + return false; + } auto autoRecordExpireTime = (m_expiredInSeconds != 0); auto time = autoRecordExpireTime ? getCurrentTimeInSecond() + m_expiredInSeconds : 0; @@ -1685,6 +1705,10 @@ bool MMKV::disableAutoKeyExpire() { if (!m_metaInfo->hasFlag(MMKVMetaInfo::EnableKeyExipre)) { return true; } + if (isReadOnly()) { + MMKVWarning("[%s] file readonly", m_mmapID.c_str()); + return false; + } MMKVInfo("erase previous recorded expire date for all keys inside [%s]", m_mmapID.c_str()); m_metaInfo->unsetFlag(MMKVMetaInfo::EnableKeyExipre); diff --git a/Core/MMKV_OSX.cpp b/Core/MMKV_OSX.cpp index cf0db9c4..795f5bf8 100644 --- a/Core/MMKV_OSX.cpp +++ b/Core/MMKV_OSX.cpp @@ -300,9 +300,13 @@ NSArray *MMKV::allKeys(bool filterExpire) { return keys; } -void MMKV::removeValuesForKeys(NSArray *arrKeys) { +bool MMKV::removeValuesForKeys(NSArray *arrKeys) { + if (isReadOnly()) { + MMKVWarning("[%s] file readonly", m_mmapID.c_str()); + return false; + } if (arrKeys.count == 0) { - return; + return true; } if (arrKeys.count == 1) { return removeValueForKey(arrKeys[0]); @@ -337,8 +341,9 @@ void MMKV::removeValuesForKeys(NSArray *arrKeys) { if (deleteCount > 0) { m_hasFullWriteback = false; - fullWriteback(); + return fullWriteback(); } + return true; } void MMKV::enumerateKeys(EnumerateBlock block) { diff --git a/Core/MemoryFile.cpp b/Core/MemoryFile.cpp index e388e708..aa2fd33f 100644 --- a/Core/MemoryFile.cpp +++ b/Core/MemoryFile.cpp @@ -50,7 +50,10 @@ File::File(MMKVPath_t path, OpenFlag flag) : m_path(std::move(path)), m_fd(-1), open(); } -MemoryFile::MemoryFile(MMKVPath_t path, size_t expectedCapacity) : m_diskFile(std::move(path), OpenFlag::ReadWrite | OpenFlag::Create), m_ptr(nullptr), m_size(0) { +MemoryFile::MemoryFile(MMKVPath_t path, size_t expectedCapacity, bool readOnly) + : m_diskFile(std::move(path), readOnly ? OpenFlag::ReadOnly : (OpenFlag::ReadWrite | OpenFlag::Create)) + , m_ptr(nullptr), m_size(0), m_readOnly(readOnly) +{ reloadFromFile(expectedCapacity); } # endif // !defined(MMKV_ANDROID) @@ -61,7 +64,7 @@ void tryResetFileProtection(const string &path); static int OpenFlag2NativeFlag(OpenFlag flag) { int native = O_CLOEXEC; - if (flag & OpenFlag::ReadWrite) { + if ((flag & OpenFlagRWMask) == OpenFlag::ReadWrite) { native |= O_RDWR; } else if (flag & OpenFlag::ReadOnly) { native |= O_RDONLY; @@ -92,10 +95,10 @@ bool File::open() { } m_fd = ::open(m_path.c_str(), OpenFlag2NativeFlag(m_flag), S_IRWXU); if (!isFileValid()) { - MMKVError("fail to open [%s], %d(%s)", m_path.c_str(), errno, strerror(errno)); + MMKVError("fail to open [%s], flag %x, %d(%s)", m_path.c_str(), m_flag, errno, strerror(errno)); return false; } - MMKVInfo("open fd[%p], %s", m_fd, m_path.c_str()); + MMKVInfo("open fd[%p], flag %x, %s", m_fd, m_flag, m_path.c_str()); return true; } @@ -128,6 +131,10 @@ bool MemoryFile::truncate(size_t size) { if (size == m_size) { return true; } + if (m_readOnly) { + // truncate readonly file not allow + return false; + } # ifdef MMKV_ANDROID if (m_diskFile.m_fileType == MMFILE_TYPE_ASHMEM) { if (size > m_size) { @@ -182,6 +189,10 @@ bool MemoryFile::truncate(size_t size) { } bool MemoryFile::msync(SyncFlag syncFlag) { + if (m_readOnly) { + // there's no point in msync() readonly memory + return true; + } if (m_ptr) { auto ret = ::msync(m_ptr, m_size, syncFlag ? MS_SYNC : MS_ASYNC); if (ret == 0) { @@ -194,9 +205,10 @@ bool MemoryFile::msync(SyncFlag syncFlag) { bool MemoryFile::mmap() { auto oldPtr = m_ptr; - m_ptr = (char *) ::mmap(m_ptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_diskFile.m_fd, 0); + auto mode = m_readOnly ? PROT_READ : (PROT_READ | PROT_WRITE); + m_ptr = (char *) ::mmap(m_ptr, m_size, mode, MAP_SHARED, m_diskFile.m_fd, 0); if (m_ptr == MAP_FAILED) { - MMKVError("fail to mmap [%s], %s", m_diskFile.m_path.c_str(), strerror(errno)); + MMKVError("fail to mmap [%s], mode %x, %s", m_diskFile.m_path.c_str(), mode, strerror(errno)); m_ptr = nullptr; return false; } @@ -226,7 +238,7 @@ void MemoryFile::reloadFromFile(size_t expectedCapacity) { mmkv::getFileSize(m_diskFile.m_fd, m_size); size_t expectedSize = std::max(DEFAULT_MMAP_SIZE, roundUp(expectedCapacity, DEFAULT_MMAP_SIZE)); // round up to (n * pagesize) - if (m_size < expectedSize || (m_size % DEFAULT_MMAP_SIZE != 0)) { + if (!m_readOnly && (m_size < expectedSize || (m_size % DEFAULT_MMAP_SIZE != 0))) { InterProcessLock exclusiveLock(&fileLock, ExclusiveLockType); SCOPED_LOCK(&exclusiveLock); @@ -240,7 +252,9 @@ void MemoryFile::reloadFromFile(size_t expectedCapacity) { } } # ifdef MMKV_IOS - tryResetFileProtection(m_diskFile.m_path); + if (!m_readOnly) { + tryResetFileProtection(m_diskFile.m_path); + } # endif } } diff --git a/Core/MemoryFile.h b/Core/MemoryFile.h index 5f461c60..8f877ebe 100644 --- a/Core/MemoryFile.h +++ b/Core/MemoryFile.h @@ -47,6 +47,7 @@ enum class OpenFlag : uint32_t { Excel = 1 << 3, // fail if Create is set but the file already exist Truncate = 1 << 4, }; +constexpr uint32_t OpenFlagRWMask = 0x3; // mask for Read Write mode static inline OpenFlag operator | (OpenFlag left, OpenFlag right) { return static_cast(static_cast(left) | static_cast(right)); @@ -56,6 +57,10 @@ static inline bool operator & (OpenFlag left, OpenFlag right) { return ((static_cast(left) & static_cast(right)) != 0); } +static inline OpenFlag operator & (OpenFlag left, uint32_t right) { + return static_cast(static_cast(left) & right); +} + template T roundUp(T numToRound, T multiple) { return ((numToRound + multiple - 1) / multiple) * multiple; @@ -109,6 +114,7 @@ class MemoryFile { #endif void *m_ptr; size_t m_size; + const bool m_readOnly; bool mmap(); @@ -116,9 +122,9 @@ class MemoryFile { public: #ifndef MMKV_ANDROID - explicit MemoryFile(MMKVPath_t path, size_t expectedCapacity = 0); + explicit MemoryFile(MMKVPath_t path, size_t expectedCapacity = 0, bool readOnly = false); #else - MemoryFile(MMKVPath_t path, size_t size, FileType fileType, size_t expectedCapacity = 0); + MemoryFile(MMKVPath_t path, size_t size, FileType fileType, size_t expectedCapacity = 0, bool readOnly = false); explicit MemoryFile(MMKVFileHandle_t ashmemFD); const FileType m_fileType; diff --git a/Core/MemoryFile_Android.cpp b/Core/MemoryFile_Android.cpp index e5198756..452c7c13 100644 --- a/Core/MemoryFile_Android.cpp +++ b/Core/MemoryFile_Android.cpp @@ -70,8 +70,9 @@ File::File(MMKVFileHandle_t ashmemFD) } } -MemoryFile::MemoryFile(string path, size_t size, FileType fileType, size_t expectedCapacity) - : m_diskFile(std::move(path), OpenFlag::ReadWrite | OpenFlag::Create, size, fileType), m_ptr(nullptr), m_size(0), m_fileType(fileType) { +MemoryFile::MemoryFile(string path, size_t size, FileType fileType, size_t expectedCapacity, bool isReadOnly) + : m_diskFile(std::move(path), isReadOnly ? OpenFlag::ReadOnly : (OpenFlag::ReadWrite | OpenFlag::Create), size, fileType), + m_ptr(nullptr), m_size(0), m_fileType(fileType), m_readOnly(isReadOnly) { if (m_fileType == MMFILE_TYPE_FILE) { reloadFromFile(expectedCapacity); } else { @@ -86,7 +87,7 @@ MemoryFile::MemoryFile(string path, size_t size, FileType fileType, size_t expec } MemoryFile::MemoryFile(int ashmemFD) - : m_diskFile(ashmemFD), m_ptr(nullptr), m_size(0), m_fileType(MMFILE_TYPE_ASHMEM) { + : m_diskFile(ashmemFD), m_ptr(nullptr), m_size(0), m_fileType(MMFILE_TYPE_ASHMEM), m_readOnly(false) { if (!m_diskFile.isFileValid()) { MMKVError("fd %d invalid", ashmemFD); } else { diff --git a/Core/MemoryFile_Win32.cpp b/Core/MemoryFile_Win32.cpp index a7a99540..8bec794b 100755 --- a/Core/MemoryFile_Win32.cpp +++ b/Core/MemoryFile_Win32.cpp @@ -43,7 +43,7 @@ File::File(MMKVPath_t path, OpenFlag flag) : m_path(std::move(path)), m_fd(INVAL static pair OpenFlag2NativeFlag(OpenFlag flag) { int access = 0, create = OPEN_EXISTING; - if (flag & OpenFlag::ReadWrite) { + if ((flag & OpenFlagRWMask) == OpenFlag::ReadWrite) { access = (GENERIC_READ | GENERIC_WRITE); } else if (flag & OpenFlag::ReadOnly) { access |= GENERIC_READ; @@ -70,10 +70,10 @@ bool File::open() { m_fd = CreateFile(m_path.c_str(), pair.first, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, pair.second, FILE_ATTRIBUTE_NORMAL, nullptr); if (!isFileValid()) { - MMKVError("fail to open:[%ls], %d", m_path.c_str(), GetLastError()); + MMKVError("fail to open:[%ls], flag %x, error %d", m_path.c_str(), m_flag, GetLastError()); return false; } - MMKVInfo("open fd[%p], %ls", m_fd, m_path.c_str()); + MMKVInfo("open fd[%p], flag %x, %ls", m_fd, m_flag, m_path.c_str()); return true; } @@ -94,11 +94,12 @@ size_t File::getActualFileSize() const { return size; } -MemoryFile::MemoryFile(MMKVPath_t path, size_t expectedCapacity) - : m_diskFile(std::move(path), OpenFlag::ReadWrite | OpenFlag::Create) +MemoryFile::MemoryFile(MMKVPath_t path, size_t expectedCapacity, bool readOnly) + : m_diskFile(std::move(path), readOnly ? OpenFlag::ReadOnly : (OpenFlag::ReadWrite | OpenFlag::Create)) , m_fileMapping(nullptr) , m_ptr(nullptr) - , m_size(0) { + , m_size(0) + , m_readOnly(readOnly) { reloadFromFile(expectedCapacity); } @@ -109,6 +110,10 @@ bool MemoryFile::truncate(size_t size) { if (size == m_size) { return true; } + if (m_readOnly) { + // truncate readonly file not allow + return false; + } auto oldSize = m_size; m_size = size; @@ -151,6 +156,10 @@ bool MemoryFile::truncate(size_t size) { } bool MemoryFile::msync(SyncFlag syncFlag) { + if (m_readOnly) { + // there's no point in msync() readonly memory + return true; + } if (m_ptr) { if (FlushViewOfFile(m_ptr, m_size)) { if (syncFlag == MMKV_SYNC) { @@ -168,14 +177,16 @@ bool MemoryFile::msync(SyncFlag syncFlag) { } bool MemoryFile::mmap() { - m_fileMapping = CreateFileMapping(m_diskFile.getFd(), nullptr, PAGE_READWRITE, 0, 0, nullptr); + auto mode = m_readOnly ? PAGE_READONLY : PAGE_READWRITE; + m_fileMapping = CreateFileMapping(m_diskFile.getFd(), nullptr, mode, 0, 0, nullptr); if (!m_fileMapping) { - MMKVError("fail to CreateFileMapping [%ls], %d", m_diskFile.m_path.c_str(), GetLastError()); + MMKVError("fail to CreateFileMapping [%ls], mode %x, %d", m_diskFile.m_path.c_str(), mode, GetLastError()); return false; } else { - m_ptr = (char *) MapViewOfFile(m_fileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); + auto viewMode = m_readOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS; + m_ptr = (char *) MapViewOfFile(m_fileMapping, viewMode, 0, 0, 0); if (!m_ptr) { - MMKVError("fail to mmap [%ls], %d", m_diskFile.m_path.c_str(), GetLastError()); + MMKVError("fail to mmap [%ls], mode %x, %d", m_diskFile.m_path.c_str(), viewMode, GetLastError()); return false; } MMKVInfo("mmap to address [%p], [%ls]", m_ptr, m_diskFile.m_path.c_str()); @@ -199,7 +210,7 @@ void MemoryFile::reloadFromFile(size_t expectedCapacity) { mmkv::getFileSize(m_diskFile.getFd(), m_size); size_t expectedSize = std::max(DEFAULT_MMAP_SIZE, roundUp(expectedCapacity, DEFAULT_MMAP_SIZE)); // round up to (n * pagesize) - if (m_size < expectedSize || (m_size % DEFAULT_MMAP_SIZE != 0)) { + if (!m_readOnly && (m_size < expectedSize || (m_size % DEFAULT_MMAP_SIZE != 0))) { size_t roundSize = ((m_size / DEFAULT_MMAP_SIZE) + 1) * DEFAULT_MMAP_SIZE;; roundSize = std::max(expectedSize, roundSize); truncate(roundSize); diff --git a/POSIX/demo/demo.cpp b/POSIX/demo/demo.cpp index 4492ac96..edac76f1 100644 --- a/POSIX/demo/demo.cpp +++ b/POSIX/demo/demo.cpp @@ -1222,6 +1222,48 @@ void testRemoveStorage() { } } +void setReadOnly(const MMKVPath_t& path, bool readOnly) { + int mode = 0; + // alter the read-only attribute + if (readOnly) { + mode = 0444; + } else { + mode = 0666; + } + // Set the file attributes to the new value + if (chmod(path.c_str(), mode) != 0) { + // If the function fails, print an error message + auto err = errno; + printf("Failed to set file attributes. Error code: %d, %s\n", err, strerror(err)); + } +} + +void testReadOnly() { + string mmapID = "testReadOnly"; + string aesKey = "ReadOnly+Key"; + { + auto mmkv = MMKV::mmkvWithID(mmapID, MMKV_SINGLE_PROCESS, &aesKey); + functionalTest(mmkv, false); + mmkv->close(); + } + + auto path = MMKV::getRootDir() + MMKV_PATH_SLASH + mmapID; + setReadOnly(path, true); + auto crcPath = path + ".crc"; + setReadOnly(crcPath, true); + { + auto mmkv = MMKV::mmkvWithID(mmapID, (MMKV_SINGLE_PROCESS | MMKV_READ_ONLY), &aesKey); + functionalTest(mmkv, true); + + // also check if it tolerate update operations without crash + functionalTest(mmkv, false); + + mmkv->close(); + } + setReadOnly(path, false); + setReadOnly(crcPath, false); +} + void MyLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const string &message) { auto desc = [level] { @@ -1280,4 +1322,5 @@ int main() { testAutoExpiration(); // testFtruncateFail(); testRemoveStorage(); + testReadOnly(); } diff --git a/POSIX/golang/golang-bridge.cpp b/POSIX/golang/golang-bridge.cpp index 7a8f6179..a6418c19 100644 --- a/POSIX/golang/golang-bridge.cpp +++ b/POSIX/golang/golang-bridge.cpp @@ -677,4 +677,20 @@ MMKV_EXPORT bool removeStorage(GoStringWrap_t mmapID, GoStringWrap_t rootPath) { return MMKV::removeStorage(id, nullptr); } +MMKV_EXPORT bool isMultiProcess(void *handle) { + MMKV *kv = static_cast(handle); + if (kv) { + return kv->isMultiProcess(); + } + return false; +} + +MMKV_EXPORT bool isReadOnly(void *handle) { + MMKV *kv = static_cast(handle); + if (kv) { + return kv->isReadOnly(); + } + return false; +} + #endif // CGO diff --git a/POSIX/golang/golang-bridge.h b/POSIX/golang/golang-bridge.h index 106a1f4d..d463110f 100644 --- a/POSIX/golang/golang-bridge.h +++ b/POSIX/golang/golang-bridge.h @@ -117,6 +117,9 @@ void setWantsContentChangeHandle(bool contentChange); bool removeStorage(GoStringWrap_t mmapID, GoStringWrap_t rootPath); +bool isMultiProcess(void *handle); +bool isReadOnly(void *handle); + #ifdef __cplusplus } #endif diff --git a/POSIX/golang/mmkv.go b/POSIX/golang/mmkv.go index 0ef19c20..09a52476 100644 --- a/POSIX/golang/mmkv.go +++ b/POSIX/golang/mmkv.go @@ -64,7 +64,11 @@ const ( const ( MMKV_SINGLE_PROCESS = 1 << iota - MMKV_MULTI_PROCESS + MMKV_MULTI_PROCESS = 1 << iota + context_MODE_MULTI_PROCESS = 1 << iota // not available in golang + mmkv_ASHMEM = 1 << iota // not available in golang + mmkv_BACKUP = 1 << iota // not available in golang + MMKV_READ_ONLY = 1 << iota ) const ( @@ -224,6 +228,9 @@ type MMKV interface { EnableCompareBeforeSet() bool DisableCompareBeforeSet() bool + + IsMultiProcess() bool + IsReadOnly() bool } type ctorMMKV uintptr @@ -673,3 +680,13 @@ func (kv ctorMMKV) DisableCompareBeforeSet() bool { ret := C.disableCompareBeforeSet(unsafe.Pointer(kv)) return bool(ret) } + +func (kv ctorMMKV) IsMultiProcess() bool { + ret := C.isMultiProcess(unsafe.Pointer(kv)) + return bool(ret) +} + +func (kv ctorMMKV) IsReadOnly() bool { + ret := C.isReadOnly(unsafe.Pointer(kv)) + return bool(ret) +} diff --git a/POSIX/golang/test/main.go b/POSIX/golang/test/main.go index fb384fe8..2bda8373 100644 --- a/POSIX/golang/test/main.go +++ b/POSIX/golang/test/main.go @@ -5,6 +5,7 @@ import ( //"log" "math" "time" + "os" "tencent.com/mmkv" ) @@ -31,6 +32,7 @@ func main() { testAutoExpire() testCompareBeforeSet() testRemoveStorage() + testReadOnly() } func functionalTest() { @@ -95,7 +97,11 @@ func functionalTest() { func testMMKV(mmapID string, cryptKey string, decodeOnly bool) mmkv.MMKV { kv := mmkv.MMKVWithIDAndModeAndCryptKey(mmapID, mmkv.MMKV_SINGLE_PROCESS, cryptKey) + testMMKVImp(kv, decodeOnly) + return kv +} +func testMMKVImp(kv mmkv.MMKV, decodeOnly bool) { if !decodeOnly { kv.SetBool(true, "bool") } @@ -147,8 +153,6 @@ func testMMKV(mmapID string, cryptKey string, decodeOnly bool) mmkv.MMKV { kv.RemoveKeys([]string{"int32", "int64"}) fmt.Println("all keys:", kv.AllKeys()) - - return kv } func testReKey() { @@ -334,6 +338,31 @@ func testRemoveStorage() { } } +func testReadOnly() { + mmapID := "testReadOnly" + aesKey := "ReadOnly+Key" + { + kv := mmkv.MMKVWithIDAndModeAndCryptKey(mmapID, mmkv.MMKV_SINGLE_PROCESS, aesKey) + testMMKVImp(kv, false) + kv.Close() + } + path := "/tmp/mmkv/" + mmapID + os.Chmod(path, 0444) + crcPath := path + ".crc" + os.Chmod(crcPath, 0444) + { + kv := mmkv.MMKVWithIDAndModeAndCryptKey(mmapID, (mmkv.MMKV_SINGLE_PROCESS | mmkv.MMKV_READ_ONLY), aesKey) + testMMKVImp(kv, true) + + // also check if it tolerate update operations without crash + testMMKVImp(kv, false) + + kv.Close() + } + os.Chmod(path, 0666) + os.Chmod(crcPath, 0666) +} + func logHandler(level int, file string, line int, function string, message string) { var levelStr string switch level { diff --git a/Python/demo.py b/Python/demo.py index 62814997..ac8845cb 100644 --- a/Python/demo.py +++ b/Python/demo.py @@ -5,13 +5,18 @@ import mmkv import time import tempfile +import os def functional_test(mmap_id, decode_only): # pass MMKVMode.MultiProcess to get a multi-process instance # kv = mmkv.MMKV('test_python', mmkv.MMKVMode.MultiProcess) kv = mmkv.MMKV(mmap_id) + functional_test_imp(kv, decode_only) + return kv + +def functional_test_imp(kv, decode_only): if not decode_only: kv.set(True, 'bool') print('bool = ', kv.getBool('bool')) @@ -164,7 +169,7 @@ def test_compare_before_set(): actualSize2 = kv.actualSize() print("testCompareBeforeSet: actualSize2 = ", actualSize2) if actualSize1 != actualSize2: - raise("size not match") + raise ("size not match") kv.set(False, key) print("testCompareBeforeSet: bool value = ", kv.getBool(key)) @@ -206,15 +211,37 @@ def test_remove_storage(): temp_dir = tempfile.gettempdir() rootDir = temp_dir + "/dev/mmkv_sg" - kv = mmkv.MMKV("test_remove/sg", rootDir = rootDir) + kv = mmkv.MMKV("test_remove/sg", rootDir=rootDir) kv.set(True, "bool") mmkv.MMKV.removeStorage("test_remove/sg") - kv = mmkv.MMKV("test_remove/sg", rootDir = rootDir) + kv = mmkv.MMKV("test_remove/sg", rootDir=rootDir) if kv.count() != 0: print("storage not successfully remove") +def test_read_only(): + mmap_id = "testReadOnly" + aes_key = "ReadOnly+Key" + + kv = mmkv.MMKV(mmap_id, mmkv.MMKVMode.SingleProcess, aes_key) + functional_test_imp(kv, False) + kv.close() + + path = mmkv.MMKV.rootDir() + "/" + mmap_id + os.chmod(path, 0o444) + crc_path = path + ".crc" + os.chmod(crc_path, 0o444) + + kv = mmkv.MMKV(mmap_id, mmkv.MMKVMode(mmkv.MMKVMode.SingleProcess | mmkv.MMKVMode.ReadOnly), aes_key) + functional_test_imp(kv, True) + functional_test_imp(kv, False) + kv.close() + + os.chmod(path, 0o666) + os.chmod(crc_path, 0o666) + + def logger(log_level, file, line, function, message): level = {mmkv.MMKVLogLevel.NoLog: 'N', mmkv.MMKVLogLevel.Debug: 'D', @@ -236,6 +263,7 @@ def content_change_handler(mmap_id): if __name__ == '__main__': temp_dir = tempfile.gettempdir() root_dir = temp_dir + '/mmkv' + print("root dir:", root_dir) # you can enable logging & log handler # mmkv.MMKV.initializeMMKV(root_dir, mmkv.MMKVLogLevel.Info, logger) @@ -261,6 +289,7 @@ def content_change_handler(mmap_id): test_auto_expire() test_compare_before_set() test_remove_storage() + test_read_only() # mmkv.MMKV.unRegisterLogHandler() # mmkv.MMKV.unRegisterErrorHandler() diff --git a/Python/libmmkv_python.cpp b/Python/libmmkv_python.cpp index 4a109751..b2becc14 100644 --- a/Python/libmmkv_python.cpp +++ b/Python/libmmkv_python.cpp @@ -67,9 +67,10 @@ static void MyContentChangeHandler(const std::string &mmapID) { PYBIND11_MODULE(mmkv, m) { m.doc() = "An efficient, small key-value storage framework developed by WeChat Team."; - py::enum_(m, "MMKVMode") + py::enum_(m, "MMKVMode", py::arithmetic()) .value("SingleProcess", MMKVMode::MMKV_SINGLE_PROCESS) .value("MultiProcess", MMKVMode::MMKV_MULTI_PROCESS) + .value("ReadOnly", MMKVMode::MMKV_READ_ONLY) .export_values(); py::enum_(m, "MMKVLogLevel") @@ -143,7 +144,7 @@ PYBIND11_MODULE(mmkv, m) { "a generic purpose instance", py::arg("mode") = MMKV_SINGLE_PROCESS, py::arg("cryptKey") = string()); clsMMKV.def("mmapID", &MMKV::mmapID); - clsMMKV.def_readonly("isInterProcess", &MMKV::m_isInterProcess); + clsMMKV.def("isInterProcess", &MMKV::isMultiProcess); clsMMKV.def("cryptKey", &MMKV::cryptKey); clsMMKV.def("reKey", &MMKV::reKey, @@ -267,6 +268,13 @@ PYBIND11_MODULE(mmkv, m) { clsMMKV.def("unlock", &MMKV::unlock); clsMMKV.def("try_lock", &MMKV::try_lock, "try to get exclusive access"); + clsMMKV.def("isMultiProcess", &MMKV::isMultiProcess, "check multi-process mode"); + clsMMKV.def("isReadOnly", &MMKV::isReadOnly, "check read-only mode"); + + clsMMKV.def("close", &MMKV::close, "close the instance"); + + clsMMKV.def_static("rootDir", &MMKV::getRootDir, "get the root directory of MMKV"); + // log callback handler clsMMKV.def_static( "registerLogHandler", diff --git a/Win32/Win32Demo/Win32Demo.cpp b/Win32/Win32Demo/Win32Demo.cpp index 70c40b9d..d0e4fd26 100755 --- a/Win32/Win32Demo/Win32Demo.cpp +++ b/Win32/Win32Demo/Win32Demo.cpp @@ -367,6 +367,56 @@ void testRemoveStorage() { } } +void setReadOnly(const MMKVPath_t& path, bool readOnly) { + // Get the current file attributes + DWORD attributes = GetFileAttributes(path.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES) { + // If the function fails, print an error message + DWORD error = GetLastError(); + printf("Failed to get file attributes. Error code: %lu\n", error); + return; + } + + // alter the read-only attribute + if (readOnly) { + attributes |= FILE_ATTRIBUTE_READONLY; + } else { + attributes &= ~FILE_ATTRIBUTE_READONLY; + } + // Set the file attributes to the new value + if (!SetFileAttributes(path.c_str(), attributes)) { + // If the function fails, print an error message + DWORD error = GetLastError(); + printf("Failed to set file attributes. Error code: %lu\n", error); + } +} + +void testReadOnly() { + string mmapID = "testReadOnly"; + string aesKey = "ReadOnly+Key"; + { + auto mmkv = MMKV::mmkvWithID(mmapID, MMKV_SINGLE_PROCESS, &aesKey); + functionalTest(mmkv, false); + mmkv->close(); + } + + auto path = MMKV::getRootDir() + MMKV_PATH_SLASH + string2MMKVPath_t(mmapID); + setReadOnly(path, true); + auto crcPath = path + L".crc"; + setReadOnly(crcPath, true); + { + auto mmkv = MMKV::mmkvWithID(mmapID, (MMKV_SINGLE_PROCESS | MMKV_READ_ONLY), &aesKey); + functionalTest(mmkv, true); + + // also check if it tolerate update operations without crash + functionalTest(mmkv, false); + + mmkv->close(); + } + setReadOnly(path, false); + setReadOnly(crcPath, false); +} + static void LogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) { @@ -417,4 +467,5 @@ int main() { testAutoExpire(); testExpectedCapacity(); testRemoveStorage(); + testReadOnly(); } diff --git a/flutter/mmkv/example/lib/main.dart b/flutter/mmkv/example/lib/main.dart index e7b52f66..a9a6412d 100644 --- a/flutter/mmkv/example/lib/main.dart +++ b/flutter/mmkv/example/lib/main.dart @@ -26,6 +26,7 @@ import "dart:typed_data"; import "package:flutter/material.dart"; import "package:mmkv/mmkv.dart"; import "package:path_provider_foundation/path_provider_foundation.dart"; +// import "package:posix/posix.dart" show chmod; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -77,7 +78,8 @@ class _MyAppState extends State { Text("MMKV Version: ${MMKV.version}\n"), TextButton( onPressed: () { - functionalTest(); + // functionalTest(); + testReadOnly(); }, child: Text("Functional Test", style: TextStyle(fontSize: 18))), TextButton( @@ -197,7 +199,11 @@ class _MyAppState extends State { MMKV testMMKV(String mmapID, String? cryptKey, bool decodeOnly, String? rootPath) { final mmkv = MMKV(mmapID, cryptKey: cryptKey, rootDir: rootPath); + testMMKVImp(mmkv, decodeOnly); + return mmkv; + } + void testMMKVImp(MMKV mmkv, bool decodeOnly) { if (!decodeOnly) { mmkv.encodeBool("bool", true); } @@ -254,8 +260,6 @@ class _MyAppState extends State { print('after remove, contains "bool": ${mmkv.containsKey('bool')}'); mmkv.removeValues(["int32", "int"]); print("all keys: ${mmkv.allKeys}"); - - return mmkv; } void testReKey() { @@ -421,4 +425,32 @@ class _MyAppState extends State { print("storage not successfully removed"); } } + + void testReadOnly() { + final name = "testReadOnly"; + final key = "ReadOnly+Key"; + { + final mmkv = MMKV(name, cryptKey: key); + testMMKVImp(mmkv, false); + mmkv.close(); + } + + // posix.dart not working in Android or iOS, sigh.. + /*final path = MMKV.rootDir + "/" + name; + chmod(path, "444"); + final crcPath = path + ".crc"; + chmod(crcPath, "444"); + */ + + final mmkv = MMKV(name, cryptKey: key, readOnly: true); + testMMKVImp(mmkv, true); + + // also check if it tolerate update operations without crash + testMMKVImp(mmkv, false); + + /* + chmod(path, "666"); + chmod(crcPath, "666"); + */ + } } diff --git a/flutter/mmkv/example/pubspec.yaml b/flutter/mmkv/example/pubspec.yaml index 6acf961a..4542529c 100644 --- a/flutter/mmkv/example/pubspec.yaml +++ b/flutter/mmkv/example/pubspec.yaml @@ -26,6 +26,8 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.0 +# posix: ^6.0.1 + dev_dependencies: flutter_test: sdk: flutter diff --git a/flutter/mmkv/lib/mmkv.dart b/flutter/mmkv/lib/mmkv.dart index 5b8a1270..40c322f5 100644 --- a/flutter/mmkv/lib/mmkv.dart +++ b/flutter/mmkv/lib/mmkv.dart @@ -38,6 +38,7 @@ enum MMKVMode { SINGLE_PROCESS_MODE, MULTI_PROCESS_MODE, } +final int _READ_ONLY_MODE = 1 << 5; /// A native memory buffer, must call [MMBuffer.destroy()] after no longer use. class MMBuffer { @@ -189,13 +190,15 @@ class MMKV { /// * You can get a multi-process MMKV instance by passing [MMKVMode.MULTI_PROCESS_MODE]. /// * You can encrypt with [cryptKey], which limits to 16 bytes at most. /// * You can customize the [rootDir] of the file. - MMKV(String mmapID, {MMKVMode mode = MMKVMode.SINGLE_PROCESS_MODE, String? cryptKey, String? rootDir, int expectedCapacity = 0}) { + MMKV(String mmapID, {MMKVMode mode = MMKVMode.SINGLE_PROCESS_MODE, String? cryptKey, String? rootDir, int expectedCapacity = 0 + , bool readOnly = false}) { if (mmapID.isNotEmpty) { final mmapIDPtr = _string2Pointer(mmapID); final cryptKeyPtr = _string2Pointer(cryptKey); final rootDirPtr = _string2Pointer(rootDir); - _handle = _getMMKVWithID(mmapIDPtr, mode.index, cryptKeyPtr, rootDirPtr, expectedCapacity); + final realMode = readOnly ? (mode.index | _READ_ONLY_MODE) : mode.index; + _handle = _getMMKVWithID(mmapIDPtr, realMode, cryptKeyPtr, rootDirPtr, expectedCapacity); calloc.free(mmapIDPtr); calloc.free(cryptKeyPtr); @@ -518,6 +521,14 @@ class MMKV { return _actualSize(_handle); } + bool get isMultiProcess { + return _isMultiProcess(_handle); + } + + bool get isReadOnly { + return _isReadOnly(_handle); + } + void removeValue(String key) { final keyPtr = key.toNativeUtf8(); _removeValueForKey(_handle, keyPtr); @@ -865,3 +876,7 @@ final bool Function(Pointer) _enableCompareBeforeSet = _mmkvPlatform.enabl final bool Function(Pointer) _disableCompareBeforeSet = _mmkvPlatform.disableCompareBeforeSetFunc(); final int Function(Pointer mmapID, Pointer rootPath) _removeStorage = _mmkvPlatform.removeStorageFunc(); + +final bool Function(Pointer) _isMultiProcess = _mmkvPlatform.isMultiProcessFunc(); + +final bool Function(Pointer) _isReadOnly = _mmkvPlatform.isReadOnlyFunc(); diff --git a/flutter/mmkv/pubspec.yaml b/flutter/mmkv/pubspec.yaml index 35a66000..2f03f78f 100644 --- a/flutter/mmkv/pubspec.yaml +++ b/flutter/mmkv/pubspec.yaml @@ -1,6 +1,6 @@ name: mmkv description: An efficient, small mobile key-value storage framework developed by WeChat. Works on Android & iOS. -version: 1.3.9 +version: 2.0.0 homepage: https://github.com/Tencent/mmkv repository: https://github.com/Tencent/MMKV/tree/master/flutter/mmkv @@ -13,17 +13,17 @@ dependencies: sdk: flutter ffi: ^2.0.0 mmkv_ios: - ^1.0.5 -# path: ../mmkv_ios +# ^1.0.6 + path: ../mmkv_ios mmkv_android: - ^1.0.5 -# path: ../mmkv_android +# ^1.0.6 + path: ../mmkv_android mmkv_ohos: - ^1.0.1 -# path: ../mmkv_ohos +# ^1.0.2 + path: ../mmkv_ohos mmkv_platform_interface: - ^1.0.1 -# path: ../mmkv_platform_interface +# ^1.0.2 + path: ../mmkv_platform_interface dev_dependencies: test: diff --git a/flutter/mmkv_android/CHANGELOG.md b/flutter/mmkv_android/CHANGELOG.md index 867f35e7..b132f473 100644 --- a/flutter/mmkv_android/CHANGELOG.md +++ b/flutter/mmkv_android/CHANGELOG.md @@ -1,4 +1,7 @@ # MMKV Platform Android Change Log +## v1.0.6 / 2024-09-xx +Keep up with native lib v2.0.0. + ## v1.0.5 / 2024-07-26 Keep up with native lib v1.3.9. diff --git a/flutter/mmkv_android/android/build.gradle b/flutter/mmkv_android/android/build.gradle index 83fbdab5..71caa92a 100644 --- a/flutter/mmkv_android/android/build.gradle +++ b/flutter/mmkv_android/android/build.gradle @@ -16,7 +16,7 @@ rootProject.allprojects { repositories { google() mavenCentral() -// mavenLocal() + mavenLocal() } } @@ -34,6 +34,6 @@ android { } dependencies { - implementation 'com.tencent:mmkv:[1.3.9, 2.0)' + implementation 'com.tencent:mmkv:[2.0.0, 2.1)' } } diff --git a/flutter/mmkv_android/pubspec.yaml b/flutter/mmkv_android/pubspec.yaml index e790f92b..395a07ee 100644 --- a/flutter/mmkv_android/pubspec.yaml +++ b/flutter/mmkv_android/pubspec.yaml @@ -1,7 +1,7 @@ name: mmkv_android description: Android platform implementation of MMKV. repository: https://github.com/Tencent/MMKV/tree/master/flutter/mmkv_ios -version: 1.0.5 +version: 1.0.6 homepage: https://github.com/Tencent/mmkv environment: @@ -13,8 +13,8 @@ dependencies: sdk: flutter ffi: ^2.0.0 mmkv_platform_interface: - ^1.0.1 -# path: ../mmkv_platform_interface +# ^1.0.2 + path: ../mmkv_platform_interface dev_dependencies: test: diff --git a/flutter/mmkv_ios/CHANGELOG.md b/flutter/mmkv_ios/CHANGELOG.md index 7c2be13d..b5b59848 100644 --- a/flutter/mmkv_ios/CHANGELOG.md +++ b/flutter/mmkv_ios/CHANGELOG.md @@ -1,4 +1,7 @@ # MMKV Platform iOS Change Log +## v1.0.6 / 2024-09-xx +Keep up with native lib v2.0.0. + ## v1.0.5 / 2024-07-26 Keep up with native lib v1.3.9. diff --git a/flutter/mmkv_ios/ios/Classes/flutter-bridge.mm b/flutter/mmkv_ios/ios/Classes/flutter-bridge.mm index 97b23d6c..a94a03dd 100644 --- a/flutter/mmkv_ios/ios/Classes/flutter-bridge.mm +++ b/flutter/mmkv_ios/ios/Classes/flutter-bridge.mm @@ -521,6 +521,22 @@ MMKV_EXPORT bool MMKV_FUNC(removeStorage)(const char *mmapID, const char *rootDi return [MMKV removeStorage:strID rootPath:nil]; } +MMKV_EXPORT bool MMKV_FUNC(isMultiProcess)(const void *handle) { + MMKV *kv = (__bridge MMKV *) handle; + if (kv) { + return [kv isMultiProcess]; + } + return false; +} + +MMKV_EXPORT bool MMKV_FUNC(isReadOnly)(const void *handle) { + MMKV *kv = (__bridge MMKV *) handle; + if (kv) { + return [kv isReadOnly]; + } + return false; +} + /* Looks like Dart:ffi's async callback not working perfectly * We don't support them for the moment. * https://github.com/dart-lang/sdk/issues/37022 diff --git a/flutter/mmkv_ios/ios/mmkv_ios.podspec b/flutter/mmkv_ios/ios/mmkv_ios.podspec index b7abd2f7..b5845189 100644 --- a/flutter/mmkv_ios/ios/mmkv_ios.podspec +++ b/flutter/mmkv_ios/ios/mmkv_ios.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.name = 'mmkv_ios' - s.version = '1.0.3' + s.version = '1.0.6' s.summary = 'MMKV is a cross-platform key-value storage framework developed by WeChat.' s.description = <<-DESC The MMKV, for Flutter. @@ -19,7 +19,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.dependency 'MMKV', '>= 1.3.9', '< 2.0' + s.dependency 'MMKV', '>= 2.0.0', '< 2.1' s.platform = :ios, '12.0' # Flutter.framework does not contain a i386 slice. diff --git a/flutter/mmkv_ios/pubspec.yaml b/flutter/mmkv_ios/pubspec.yaml index 1bc4babb..e203b7ca 100644 --- a/flutter/mmkv_ios/pubspec.yaml +++ b/flutter/mmkv_ios/pubspec.yaml @@ -1,7 +1,7 @@ name: mmkv_ios description: iOS platform implementation of MMKV. repository: https://github.com/Tencent/MMKV/tree/master/flutter/mmkv_ios -version: 1.0.5 +version: 1.0.6 homepage: https://github.com/Tencent/mmkv environment: @@ -13,8 +13,8 @@ dependencies: sdk: flutter ffi: ^2.0.0 mmkv_platform_interface: - ^1.0.1 -# path: ../mmkv_platform_interface +# ^1.0.2 + path: ../mmkv_platform_interface dev_dependencies: test: diff --git a/flutter/mmkv_ohos/CHANGELOG.md b/flutter/mmkv_ohos/CHANGELOG.md index 756b294a..4669280d 100644 --- a/flutter/mmkv_ohos/CHANGELOG.md +++ b/flutter/mmkv_ohos/CHANGELOG.md @@ -1,4 +1,7 @@ # MMKV Platform OHOS Change Log +## v1.0.2 / 2024-09-xx +Keep up with native lib v2.0.0. + ## v1.0.1 / 2024-07-26 Keep up with native lib v1.3.9. diff --git a/flutter/mmkv_ohos/ohos/oh-package.json5 b/flutter/mmkv_ohos/ohos/oh-package.json5 index 87d2c3fc..1c366bce 100644 --- a/flutter/mmkv_ohos/ohos/oh-package.json5 +++ b/flutter/mmkv_ohos/ohos/oh-package.json5 @@ -7,7 +7,7 @@ "license": "Apache-2.0", "dependencies": { "@ohos/flutter_ohos": "file:./har/flutter.har", - "@tencent/mmkv": "^1.3.9" + "@tencent/mmkv": "^2.0.0" // "@tencent/mmkv": "file:../../../OpenHarmony/MMKV/build/default/outputs/default/MMKV.har" }, "devDependencies": {}, diff --git a/flutter/mmkv_ohos/pubspec.yaml b/flutter/mmkv_ohos/pubspec.yaml index 90802694..123b6ea1 100644 --- a/flutter/mmkv_ohos/pubspec.yaml +++ b/flutter/mmkv_ohos/pubspec.yaml @@ -1,7 +1,7 @@ name: mmkv_ohos description: OHOS platform implementation of MMKV. repository: https://github.com/Tencent/MMKV/tree/master/flutter/mmkv_ohos -version: 1.0.1 +version: 1.0.2 homepage: https://github.com/Tencent/mmkv environment: @@ -18,8 +18,8 @@ dependencies: # path: "packages/path_provider/path_provider_ohos" ffi: ^2.0.0 mmkv_platform_interface: - ^1.0.1 -# path: ../mmkv_platform_interface +# ^1.0.2 + path: ../mmkv_platform_interface dev_dependencies: flutter_test: diff --git a/flutter/mmkv_platform_interface/CHANGELOG.md b/flutter/mmkv_platform_interface/CHANGELOG.md index ccf49100..98815448 100644 --- a/flutter/mmkv_platform_interface/CHANGELOG.md +++ b/flutter/mmkv_platform_interface/CHANGELOG.md @@ -1,4 +1,8 @@ # MMKV Platform Interface Change Log +## v1.0.2 / 2024-09-xx +* Add `isMultiProcess()`. +* Add `isReadOnly()`. + ## v1.0.1 / 2024-07-12 * Add override point for path_provider. diff --git a/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart b/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart index a4435f09..603f93fb 100644 --- a/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart +++ b/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart @@ -294,4 +294,13 @@ class MMKVPluginPlatformFFI extends MMKVPluginPlatform { int Function(Pointer mmapID, Pointer rootPath) removeStorageFunc() { return nativeLib().lookup, Pointer)>>(nativeFuncName("removeStorage")).asFunction(); } + + @override + bool Function(Pointer) isMultiProcessFunc() { + return nativeLib().lookup)>>(nativeFuncName("isMultiProcess")).asFunction(); + } + + bool Function(Pointer) isReadOnlyFunc() { + return nativeLib().lookup)>>(nativeFuncName("isReadOnly")).asFunction(); + } } diff --git a/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart b/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart index 7680a56d..49912da7 100644 --- a/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart +++ b/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart @@ -228,6 +228,14 @@ class MMKVPluginPlatform { throw UnimplementedError(); } + bool Function(Pointer) isMultiProcessFunc() { + throw UnimplementedError(); + } + + bool Function(Pointer) isReadOnlyFunc() { + throw UnimplementedError(); + } + // some platform doesn't publish their path_provider package to pub.dev // provide override point for these calls Future getApplicationDocumentsPath() async { diff --git a/flutter/mmkv_platform_interface/pubspec.yaml b/flutter/mmkv_platform_interface/pubspec.yaml index 9a03eb9c..02aeb892 100644 --- a/flutter/mmkv_platform_interface/pubspec.yaml +++ b/flutter/mmkv_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the MMKV plugin. repository: https://github.com/Tencent/MMKV/tree/master/flutter/mmkv_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.0.1 +version: 1.0.2 homepage: https://github.com/Tencent/MMKV environment: diff --git a/iOS/MMKV/MMKV/MMKV.h b/iOS/MMKV/MMKV/MMKV.h index 071ccc90..0a42d5b9 100644 --- a/iOS/MMKV/MMKV/MMKV.h +++ b/iOS/MMKV/MMKV/MMKV.h @@ -25,8 +25,10 @@ #endif typedef NS_ENUM(NSUInteger, MMKVMode) { - MMKVSingleProcess = 0x1, - MMKVMultiProcess = 0x2, + MMKVSingleProcess = 1 << 0, + MMKVMultiProcess = 1 << 1, + // 2~4 are preserved for Android + MMKVReadOnly = 1 << 5, }; typedef NS_ENUM(UInt32, MMKVExpireDuration) { @@ -87,7 +89,7 @@ NS_ASSUME_NONNULL_BEGIN + (nullable instancetype)mmkvWithID:(NSString *)mmapID expectedCapacity:(size_t)expectedCapacity NS_SWIFT_NAME(init(mmapID:expectedCapacity:)); /// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID -/// @param mode MMKVMultiProcess for multi-process MMKV +/// @param mode MMKVReadOnly for readonly MMKV, MMKVMultiProcess for multi-process MMKV + (nullable instancetype)mmkvWithID:(NSString *)mmapID mode:(MMKVMode)mode NS_SWIFT_NAME(init(mmapID:mode:)); /// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID @@ -101,7 +103,7 @@ NS_ASSUME_NONNULL_BEGIN /// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID /// @param cryptKey 16 bytes at most -/// @param mode MMKVMultiProcess for multi-process MMKV +/// @param mode MMKVReadOnly for readonly MMKV, MMKVMultiProcess for multi-process MMKV + (nullable instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey mode:(MMKVMode)mode NS_SWIFT_NAME(init(mmapID:cryptKey:mode:)); /// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID @@ -133,6 +135,12 @@ NS_ASSUME_NONNULL_BEGIN /// @param expectedCapacity the file size you expected when opening or creating file + (nullable instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey rootPath:(nullable NSString *)rootPath expectedCapacity:(size_t)expectedCapacity NS_SWIFT_NAME(init(mmapID:cryptKey:rootPath:expectedCapacity:)); +/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID +/// @param cryptKey 16 bytes at most +/// @param rootPath custom path of the file, `NSDocumentDirectory/mmkv` by default +/// @param mode MMKVReadOnly for readonly MMKV, MMKVMultiProcess for multi-process MMKV +/// @param expectedCapacity the file size you expected when opening or creating file ++ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey rootPath:(nullable NSString *)rootPath mode:(MMKVMode)mode expectedCapacity:(size_t)expectedCapacity NS_SWIFT_NAME(init(mmapID:cryptKey:rootPath:mode:expectedCapacity:)); /// you can call this on applicationWillTerminate, it's totally fine if you don't call + (void)onAppTerminate; @@ -311,6 +319,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)sync; - (void)async; +- (BOOL)isMultiProcess; + +- (BOOL)isReadOnly; + /// backup one MMKV instance to dstDir /// @param mmapID the MMKV ID to backup /// @param rootPath the customize root path of the MMKV, if null then backup from the root dir of MMKV diff --git a/iOS/MMKV/MMKV/libMMKV.mm b/iOS/MMKV/MMKV/libMMKV.mm index e76411be..dd0ad9f2 100644 --- a/iOS/MMKV/MMKV/libMMKV.mm +++ b/iOS/MMKV/MMKV/libMMKV.mm @@ -166,7 +166,7 @@ + (instancetype)mmkvWithID:(NSString *)mmapID expectedCapacity:(size_t)expectedC } + (instancetype)mmkvWithID:(NSString *)mmapID mode:(MMKVMode)mode { - auto rootPath = (mode == MMKVSingleProcess) ? nil : g_groupPath; + auto rootPath = (mode & MMKVSingleProcess) ? nil : g_groupPath; return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:rootPath mode:mode]; } @@ -179,7 +179,7 @@ + (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey expect } + (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey mode:(MMKVMode)mode { - auto rootPath = (mode == MMKVSingleProcess) ? nil : g_groupPath; + auto rootPath = (mode & MMKVSingleProcess) ? nil : g_groupPath; return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:rootPath mode:mode]; } @@ -219,7 +219,7 @@ + (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey rootPa SCOPED_LOCK(g_lock); - if (mode == MMKVMultiProcess) { + if (mode & MMKVMultiProcess) { if (!rootPath) { rootPath = g_groupPath; } @@ -718,6 +718,14 @@ - (void)checkContentChanged { m_mmkv->checkContentChanged(); } +- (BOOL)isMultiProcess { + return m_mmkv->isMultiProcess(); +} + +- (BOOL)isReadOnly { + return m_mmkv->isReadOnly(); +} + + (void)onAppTerminate { g_lock->lock(); @@ -1123,7 +1131,7 @@ + (BOOL)removeStorage:(NSString *)mmapID rootPath:(nullable NSString *)path NS_S } + (BOOL)removeStorage:(NSString *)mmapID mode:(MMKVMode)mode NS_SWIFT_NAME(removeStorage(for:mode:)) { - auto rootPath = (mode == MMKVSingleProcess) ? nil : g_groupPath; + auto rootPath = (mode & MMKVSingleProcess) ? nil : g_groupPath; return [self removeStorage:mmapID rootPath:rootPath]; } diff --git a/iOS/MMKVDemo/MMKVDemo.xcodeproj/project.pbxproj b/iOS/MMKVDemo/MMKVDemo.xcodeproj/project.pbxproj index 5d4bdfec..964e4398 100644 --- a/iOS/MMKVDemo/MMKVDemo.xcodeproj/project.pbxproj +++ b/iOS/MMKVDemo/MMKVDemo.xcodeproj/project.pbxproj @@ -59,6 +59,8 @@ CBB6C828215CDB9600487192 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBB6C827215CDB9600487192 /* Assets.xcassets */; }; CBB6C82B215CDB9600487192 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBB6C829215CDB9600487192 /* Main.storyboard */; }; CBB6C82E215CDB9600487192 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB6C82D215CDB9600487192 /* main.m */; }; + CBDC22E12C8ABDD20000DDA5 /* testReadOnly in Resources */ = {isa = PBXBuildFile; fileRef = CBDC22DF2C8ABDD20000DDA5 /* testReadOnly */; }; + CBDC22E22C8ABDD20000DDA5 /* testReadOnly.crc in Resources */ = {isa = PBXBuildFile; fileRef = CBDC22E02C8ABDD20000DDA5 /* testReadOnly.crc */; }; CBF19016243D6F59001C82ED /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBF19014243D6F59001C82ED /* Interface.storyboard */; }; CBF19018243D6F5A001C82ED /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBF19017243D6F5A001C82ED /* Assets.xcassets */; }; CBF1901F243D6F5A001C82ED /* WatchApp Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = CBF1901E243D6F5A001C82ED /* WatchApp Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -320,6 +322,8 @@ CBB6C82D215CDB9600487192 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; CBB6C82F215CDB9600487192 /* MMKVMacDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MMKVMacDemo.entitlements; sourceTree = ""; }; CBD5359823B4718200D3A404 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + CBDC22DF2C8ABDD20000DDA5 /* testReadOnly */ = {isa = PBXFileReference; lastKnownFileType = file; path = testReadOnly; sourceTree = ""; }; + CBDC22E02C8ABDD20000DDA5 /* testReadOnly.crc */ = {isa = PBXFileReference; lastKnownFileType = file; path = testReadOnly.crc; sourceTree = ""; }; CBF19012243D6F59001C82ED /* WatchApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WatchApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; CBF19015243D6F59001C82ED /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; }; CBF19017243D6F5A001C82ED /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -465,15 +469,11 @@ CB1FD42220455E2D00931B5F /* MMKVDemo */ = { isa = PBXGroup; children = ( - CB6D44C423B3924E00F369AA /* MMKVDemo.entitlements */, CB1FD42320455E2D00931B5F /* AppDelegate.h */, CB1FD42420455E2D00931B5F /* AppDelegate.m */, - CB1FD42C20455E2D00931B5F /* Assets.xcassets */, + CBDC22DE2C8ABD880000DDA5 /* Resources */, CB9F963820FCA7C500A1C14C /* DemoSwiftUsage.swift */, - CB1FD43120455E2D00931B5F /* Info.plist */, - CB1FD42E20455E2D00931B5F /* LaunchScreen.storyboard */, CB1FD43220455E2D00931B5F /* main.m */, - CB1FD42920455E2D00931B5F /* Main.storyboard */, CB9F963720FCA7C400A1C14C /* MMKVDemo-Bridging-Header.h */, CB1FD42620455E2D00931B5F /* ViewController.h */, CB1FD42720455E2D00931B5F /* ViewController.mm */, @@ -582,6 +582,20 @@ path = MMKVMacDemo; sourceTree = ""; }; + CBDC22DE2C8ABD880000DDA5 /* Resources */ = { + isa = PBXGroup; + children = ( + CBDC22DF2C8ABDD20000DDA5 /* testReadOnly */, + CBDC22E02C8ABDD20000DDA5 /* testReadOnly.crc */, + CB6D44C423B3924E00F369AA /* MMKVDemo.entitlements */, + CB1FD42C20455E2D00931B5F /* Assets.xcassets */, + CB1FD43120455E2D00931B5F /* Info.plist */, + CB1FD42E20455E2D00931B5F /* LaunchScreen.storyboard */, + CB1FD42920455E2D00931B5F /* Main.storyboard */, + ); + path = Resources; + sourceTree = ""; + }; CBF19013243D6F59001C82ED /* WatchApp */ = { isa = PBXGroup; children = ( @@ -913,7 +927,9 @@ buildActionMask = 2147483647; files = ( CB1FD43020455E2D00931B5F /* LaunchScreen.storyboard in Resources */, + CBDC22E22C8ABDD20000DDA5 /* testReadOnly.crc in Resources */, CB1FD42D20455E2D00931B5F /* Assets.xcassets in Resources */, + CBDC22E12C8ABDD20000DDA5 /* testReadOnly in Resources */, CB1FD42B20455E2D00931B5F /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1372,10 +1388,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = MMKVDemo/MMKVDemo.entitlements; + CODE_SIGN_ENTITLEMENTS = MMKVDemo/Resources/MMKVDemo.entitlements; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = SG5PVJM4JW; - INFOPLIST_FILE = MMKVDemo/Info.plist; + INFOPLIST_FILE = MMKVDemo/Resources/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1395,10 +1411,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = MMKVDemo/MMKVDemo.entitlements; + CODE_SIGN_ENTITLEMENTS = MMKVDemo/Resources/MMKVDemo.entitlements; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = SG5PVJM4JW; - INFOPLIST_FILE = MMKVDemo/Info.plist; + INFOPLIST_FILE = MMKVDemo/Resources/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/iOS/MMKVDemo/MMKVDemo.xcodeproj/xcshareddata/xcschemes/MMKVDemo.xcscheme b/iOS/MMKVDemo/MMKVDemo.xcodeproj/xcshareddata/xcschemes/MMKVDemo.xcscheme index acbe2cb4..1e9124f5 100644 --- a/iOS/MMKVDemo/MMKVDemo.xcodeproj/xcshareddata/xcschemes/MMKVDemo.xcscheme +++ b/iOS/MMKVDemo/MMKVDemo.xcodeproj/xcshareddata/xcschemes/MMKVDemo.xcscheme @@ -41,7 +41,7 @@