/* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ #include "CodedInputData.h" #include "CodedOutputData.h" #include "InterProcessLock.h" #include "KeyValueHolder.h" #include "MMBuffer.h" #include "MMKVLog.h" #include "MMKVMetaInfo.hpp" #include "MMKV_IO.h" #include "MMKV_OSX.h" #include "MemoryFile.h" #include "MiniPBCoder.h" #include "PBUtility.h" #include "ScopedLock.hpp" #include "ThreadLock.h" #include "aes/AESCrypt.h" #include "aes/openssl/openssl_aes.h" #include "aes/openssl/openssl_md5.h" #include "crc32/Checksum.h" #include #include #include #include #include #include #include #if defined(__aarch64__) && defined(__linux__) && !defined (MMKV_OHOS) # include # include #endif #ifdef MMKV_APPLE # if __has_feature(objc_arc) # error This file must be compiled with MRC. Use -fno-objc-arc flag. # endif # include "MMKV_OSX.h" #endif // MMKV_APPLE using namespace std; using namespace mmkv; namespace fs = std::filesystem; unordered_map *g_instanceDic; ThreadLock *g_instanceLock; MMKVPath_t g_rootDir; MMKVPath_t g_realRootDir; static ThreadLock *g_namespaceLock; static unordered_map g_realRootMap; size_t mmkv::DEFAULT_MMAP_SIZE; MMKV_NAMESPACE_BEGIN static MMKVPath_t encodeFilePath(const string &mmapID, const MMKVPath_t &rootDir); MMKVPath_t filename(const MMKVPath_t &path); #ifndef MMKV_ANDROID MMKV::MMKV(const string &mmapID, const MMKVConfig &config) : m_mmapID(mmapID) , m_mode(config.mode) , m_path(mappedKVPathWithID(m_mmapID, config.rootPath, true)) , m_crcPath(crcPathWithPath(m_path)) , m_dic(nullptr) , m_dicCrypt(nullptr) , m_expectedCapacity(std::max(DEFAULT_MMAP_SIZE, roundUp(config.expectedCapacity, DEFAULT_MMAP_SIZE))) , m_file(new MemoryFile(m_path, m_expectedCapacity, isReadOnly(), true)) , m_metaFile(new MemoryFile(m_crcPath, 0, isReadOnly(), !isMultiProcess())) , m_metaInfo(new MMKVMetaInfo()) , m_crypter(nullptr) , m_lock(new ThreadLock()) , m_fileLock(new FileLock(isMultiProcess() ? m_metaFile->getFd() : MMKVFileHandleInvalidValue)) , m_sharedProcessLock(new InterProcessLock(m_fileLock, SharedLockType)) , m_exclusiveProcessLock(new InterProcessLock(m_fileLock, ExclusiveLockType)) { m_actualSize = 0; m_output = nullptr; # ifndef MMKV_DISABLE_CRYPT auto cryptKey = config.cryptKey; if (cryptKey && !cryptKey->empty()) { m_dicCrypt = new MMKVMapCrypt(); m_crypter = new AESCrypt(cryptKey->data(), cryptKey->length(), nullptr, 0, config.aes256); } else { m_dic = new MMKVMap(); } # else m_dic = new MMKVMap(); # endif m_needLoadFromFile = true; m_hasFullWriteback = false; m_crcDigest = 0; m_lock->initialize(); m_sharedProcessLock->m_enable = isMultiProcess(); m_exclusiveProcessLock->m_enable = isMultiProcess(); m_recoverStrategic = config.recover; m_itemSizeLimit = config.itemSizeLimit; if (config.enableKeyExpire.has_value()) { configAutoExipreIfNeeded(config); } if (config.enableCompareBeforeSet) { enableCompareBeforeSet(); } } #endif MMKV::~MMKV() { clearMemoryCache(); delete m_dic; #ifndef MMKV_DISABLE_CRYPT delete m_dicCrypt; delete m_crypter; #endif delete m_metaInfo; delete m_lock; delete m_fileLock; delete m_sharedProcessLock; delete m_exclusiveProcessLock; #ifdef MMKV_ANDROID #ifndef MMKV_OHOS delete m_sharedProcessModeLock; delete m_exclusiveProcessModeLock; delete m_fileModeLock; #endif // !MMKV_OHOS delete m_sharedMigrationLock; delete m_fileMigrationLock; #endif // MMKV_ANDROID delete m_metaFile; delete m_file; MMKVInfo("destruct [%s]", m_mmapID.c_str()); } MMKV *MMKV::defaultMMKV(MMKVMode mode, const string *cryptKey, bool aes256) { auto config = MMKVConfig(); config.mode = mode; #ifndef MMKV_DISABLE_CRYPT config.aes256 = aes256; config.cryptKey = cryptKey; #else (void) cryptKey; (void) aes256; #endif return mmkvWithID(DEFAULT_MMAP_ID, config); } MMKV *MMKV::defaultMMKV(const MMKVConfig &config) { return mmkvWithID(DEFAULT_MMAP_ID, config); } static void initialize() { g_instanceDic = new unordered_map; g_instanceLock = new ThreadLock(); g_instanceLock->initialize(); mmkv::DEFAULT_MMAP_SIZE = mmkv::getPageSize(); MMKVInfo("version %s, page size %d, arch %s", MMKV_VERSION, DEFAULT_MMAP_SIZE, MMKV_ABI); // get CPU status of ARMv8 extensions (CRC32, AES) #if defined(__aarch64__) && defined(__linux__) && !defined (MMKV_OHOS) auto hwcaps = getauxval(AT_HWCAP); # ifndef MMKV_DISABLE_CRYPT if (hwcaps & HWCAP_AES) { openssl::AES_set_encrypt_key = openssl_aes_arm_set_encrypt_key; openssl::AES_set_decrypt_key = openssl_aes_arm_set_decrypt_key; openssl::AES_encrypt = openssl_aes_arm_encrypt; openssl::AES_decrypt = openssl_aes_arm_decrypt; MMKVInfo("armv8 AES instructions is supported"); } else { MMKVInfo("armv8 AES instructions is not supported"); } # endif // MMKV_DISABLE_CRYPT # ifdef MMKV_USE_ARMV8_CRC32 if (hwcaps & HWCAP_CRC32) { CRC32 = mmkv::armv8_crc32; MMKVInfo("armv8 CRC32 instructions is supported"); } else { MMKVInfo("armv8 CRC32 instructions is not supported"); } # endif // MMKV_USE_ARMV8_CRC32 #endif // __aarch64__ && defined(__linux__) && !defined (MMKV_OHOS) #if defined(MMKV_DEBUG) && !defined(MMKV_DISABLE_CRYPT) // AESCrypt::testAESCrypt(); // KeyValueHolderCrypt::testAESToMMBuffer(); #endif } static void ensureMinimalInitialize() { static ThreadOnceToken_t once_control = ThreadOnceUninitialized; ThreadLock::ThreadOnce(&once_control, initialize); } void MMKV::initializeMMKV(const MMKVPath_t &rootDir, MMKVLogLevel logLevel, mmkv::MMKVHandler *handler) { g_currentLogLevel = logLevel; g_handler = handler; ensureMinimalInitialize(); #ifdef MMKV_APPLE // crc32 instruction requires A10 chip, aka iPhone 7 or iPad 6th generation int device = 0, version = 0; GetAppleMachineInfo(device, version); MMKVInfo("Apple Device: %d, version: %d", device, version); #endif if (g_rootDir.empty()) { g_rootDir = rootDir; // avoid operating g_realRootMap directly g_realRootDir = nameSpace(rootDir).getRootDir(); mkPath(g_realRootDir); } const auto &rootDirStr = MMKVPath_t2String(g_realRootDir); MMKVInfo("root dir: %s", rootDirStr.c_str()); } const MMKVPath_t &MMKV::getRootDir() { // for backword consistency we can't return g_realRootDir return g_rootDir; } #ifndef MMKV_ANDROID MMKV *MMKV::getMMKVWithID(const std::string &mmapID, const MMKVConfig &config) { if (mmapID.empty() || !g_instanceLock) { return nullptr; } SCOPED_LOCK(g_instanceLock); auto rootPath = config.rootPath; auto mmapKey = mmapedKVKey(mmapID, rootPath, true); auto itr = g_instanceDic->find(mmapKey); if (itr != g_instanceDic->end()) { MMKV *kv = itr->second; return kv; } if (rootPath && (rootPath != &g_realRootDir) && !(config.mode & MMKV_READ_ONLY)) { MMKVPath_t specialPath = (*rootPath) + MMKV_PATH_SLASH + SPECIAL_CHARACTER_DIRECTORY_NAME; if (!isFileExist(specialPath)) { mkPath(specialPath); } } auto theRootDir = rootPath ? rootPath : &g_realRootDir; const auto &theRoot = MMKVPath_t2String(*theRootDir); MMKVInfo("prepare to load %s (id %s) from rootPath %s", mmapID.c_str(), mmapKey.c_str(), theRoot.c_str()); auto kv = new MMKV(mmapID, config); kv->m_mmapKey = mmapKey; (*g_instanceDic)[mmapKey] = kv; return kv; } #endif MMKV *MMKV::mmkvWithID(const string &mmapID, MMKVMode mode, const string *cryptKey, const MMKVPath_t *rootPath, size_t expectedCapacity, bool aes256) { MMKVConfig config; config.mode = mode; #ifndef MMKV_DISABLE_CRYPT config.aes256 = aes256; config.cryptKey = cryptKey; #endif config.rootPath = rootPath; config.expectedCapacity = expectedCapacity; return mmkvWithID(mmapID, config); } MMKV *MMKV::mmkvWithID(const std::string &mmapID, const MMKVConfig &config) { if (mmapID.empty() || !g_instanceLock) { return nullptr; } auto ns = config.rootPath ? nameSpace(*config.rootPath) : defaultNameSpace(); auto newConfig = config; newConfig.rootPath = &ns.m_rootDir; return getMMKVWithID(mmapID, newConfig); } void MMKV::onExit() { if (!g_instanceLock) { return; } SCOPED_LOCK(g_instanceLock); for (auto &pair : *g_instanceDic) { MMKV *kv = pair.second; kv->sync(); kv->clearMemoryCache(); delete kv; pair.second = nullptr; } delete g_instanceDic; g_instanceDic = nullptr; } const string &MMKV::mmapID() const { return m_mmapID; } void MMKV::notifyContentChanged() { if (g_handler) { g_handler->onContentChangedByOuterProcess(m_mmapID); } } void MMKV::notifyContentLoaded() { if (g_handler) { g_handler->onMMKVContentLoadSuccessfully(m_mmapID); } } void MMKV::checkContentChanged() { SCOPED_LOCK(m_lock); checkLoadData(); } void MMKV::clearMemoryCache(bool keepSpace) { SCOPED_LOCK(m_lock); if (m_needLoadFromFile) { return; } MMKVInfo("clearMemoryCache [%s]", m_mmapID.c_str()); m_needLoadFromFile = true; m_hasFullWriteback = false; clearDictionary(m_dic); #ifndef MMKV_DISABLE_CRYPT clearDictionary(m_dicCrypt); if (m_crypter) { // if read-only, cannot garrentee we have random iv if (m_metaInfo->m_version >= MMKVVersionRandomIV) { m_crypter->resetIV(m_metaInfo->m_vector, sizeof(m_metaInfo->m_vector)); } else { m_crypter->resetIV(); } } #endif delete m_output; m_output = nullptr; if (!keepSpace) { m_file->clearMemoryCache(); } // inter-process lock rely on MetaFile's fd, never close it // m_metaFile->clearMemoryCache(); m_actualSize = 0; m_metaInfo->m_crcDigest = 0; } void MMKV::close() { MMKVInfo("close [%s]", m_mmapID.c_str()); SCOPED_LOCK(g_instanceLock); m_lock->lock(); auto itr = g_instanceDic->find(m_mmapKey); if (itr != g_instanceDic->end()) { g_instanceDic->erase(itr); } // close() requires the caller to guarantee that no other operation or // alias will use this instance. ThreadLock's destructor releases the // recursive lock ownership held by this close path before destroying the // underlying platform lock. delete this; } #ifndef MMKV_DISABLE_CRYPT string MMKV::cryptKey() const { SCOPED_LOCK(m_lock); if (m_crypter) { char key[AES256_KEY_LEN] = {}; m_crypter->getKey(key); return {key, strnlen(key, AES256_KEY_LEN)}; } return ""; } void MMKV::checkReSetCryptKey(const string *cryptKey, bool aes256) { SCOPED_LOCK(m_lock); const auto hasNewKey = cryptKey && !cryptKey->empty(); if ((!m_crypter && !hasNewKey) || (m_crypter && hasNewKey && this->cryptKey() == *cryptKey && m_crypter->getMaxKeyLength() == (aes256 ? AES256_KEY_LEN : AES_KEY_LEN))) { return; } AESCrypt *newCrypter = nullptr; if (hasNewKey) { MMKVInfo("setting new aes key"); if (!m_dicCrypt) { m_dicCrypt = new MMKVMapCrypt(); } newCrypter = new AESCrypt(cryptKey->data(), cryptKey->length(), nullptr, 0, aes256); } else { MMKVInfo("reset aes key"); if (!m_dic) { m_dic = new MMKVMap(); } } clearMemoryCache(); delete m_crypter; m_crypter = newCrypter; checkLoadData(); } #endif // MMKV_DISABLE_CRYPT bool MMKV::isFileValid() { return m_file->isFileValid(); } // crc // assuming m_file is valid bool MMKV::checkFileCRCValid(size_t actualSize, uint32_t crcDigest) { auto ptr = (uint8_t *) m_file->getMemory(); if (ptr) { m_crcDigest = (uint32_t) CRC32(0, (const uint8_t *) ptr + Fixed32Size, (uint32_t) actualSize); if (m_crcDigest == crcDigest) { return true; } MMKVError("check crc [%s] fail, crc32:%u, m_crcDigest:%u", m_mmapID.c_str(), crcDigest, m_crcDigest); } return false; } void MMKV::recalculateCRCDigestWithIV(const void *iv) { auto ptr = (const uint8_t *) m_file->getMemory(); if (ptr) { m_crcDigest = 0; m_crcDigest = (uint32_t) CRC32(0, ptr + Fixed32Size, (uint32_t) m_actualSize); writeActualSize(m_actualSize, m_crcDigest, iv, IncreaseSequence); } } void MMKV::recalculateCRCDigestOnly() { auto ptr = (const uint8_t *) m_file->getMemory(); if (ptr) { m_crcDigest = 0; m_crcDigest = (uint32_t) CRC32(0, ptr + Fixed32Size, (uint32_t) m_actualSize); writeActualSize(m_actualSize, m_crcDigest, nullptr, KeepSequence); } } void MMKV::updateCRCDigest(const uint8_t *ptr, size_t length) { if (ptr == nullptr) { return; } m_crcDigest = (uint32_t) CRC32(m_crcDigest, ptr, (uint32_t) length); writeActualSize(m_actualSize, m_crcDigest, nullptr, KeepSequence); } // set & get bool MMKV::set(bool value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(bool value, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); size_t size = mmkv_unlikely(m_enableKeyExpire) ? Fixed32Size + pbBoolSize() : pbBoolSize(); MMBuffer data(size); CodedOutputData output(data.getPtr(), size); output.writeBool(value); if (mmkv_unlikely(m_enableKeyExpire)) { auto time = (expireDuration != ExpireNever) ? safeExpirationPlusCurrentTime(expireDuration) : ExpireNever; output.writeRawLittleEndian32(UInt32ToInt32(time)); } else { assert(expireDuration == ExpireNever && "setting expire duration without calling enableAutoKeyExpire() first"); } return setDataForKey(std::move(data), key); } bool MMKV::set(int32_t value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(int32_t value, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); size_t size = mmkv_unlikely(m_enableKeyExpire) ? Fixed32Size + pbInt32Size(value) : pbInt32Size(value); MMBuffer data(size); CodedOutputData output(data.getPtr(), size); output.writeInt32(value); if (mmkv_unlikely(m_enableKeyExpire)) { auto time = (expireDuration != ExpireNever) ? safeExpirationPlusCurrentTime(expireDuration) : ExpireNever; output.writeRawLittleEndian32(UInt32ToInt32(time)); } else { assert(expireDuration == ExpireNever && "setting expire duration without calling enableAutoKeyExpire() first"); } return setDataForKey(std::move(data), key); } bool MMKV::set(uint32_t value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(uint32_t value, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); size_t size = mmkv_unlikely(m_enableKeyExpire) ? Fixed32Size + pbUInt32Size(value) : pbUInt32Size(value); MMBuffer data(size); CodedOutputData output(data.getPtr(), size); output.writeUInt32(value); if (mmkv_unlikely(m_enableKeyExpire)) { auto time = (expireDuration != ExpireNever) ? safeExpirationPlusCurrentTime(expireDuration) : ExpireNever; output.writeRawLittleEndian32(UInt32ToInt32(time)); } else { assert(expireDuration == ExpireNever && "setting expire duration without calling enableAutoKeyExpire() first"); } return setDataForKey(std::move(data), key); } bool MMKV::set(int64_t value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(int64_t value, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); size_t size = mmkv_unlikely(m_enableKeyExpire) ? Fixed32Size + pbInt64Size(value) : pbInt64Size(value); MMBuffer data(size); CodedOutputData output(data.getPtr(), size); output.writeInt64(value); if (mmkv_unlikely(m_enableKeyExpire)) { auto time = (expireDuration != ExpireNever) ? safeExpirationPlusCurrentTime(expireDuration) : ExpireNever; output.writeRawLittleEndian32(UInt32ToInt32(time)); } else { assert(expireDuration == ExpireNever && "setting expire duration without calling enableAutoKeyExpire() first"); } return setDataForKey(std::move(data), key); } bool MMKV::set(uint64_t value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(uint64_t value, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); size_t size = mmkv_unlikely(m_enableKeyExpire) ? Fixed32Size + pbUInt64Size(value) : pbUInt64Size(value); MMBuffer data(size); CodedOutputData output(data.getPtr(), size); output.writeUInt64(value); if (mmkv_unlikely(m_enableKeyExpire)) { auto time = (expireDuration != ExpireNever) ? safeExpirationPlusCurrentTime(expireDuration) : ExpireNever; output.writeRawLittleEndian32(UInt32ToInt32(time)); } else { assert(expireDuration == ExpireNever && "setting expire duration without calling enableAutoKeyExpire() first"); } return setDataForKey(std::move(data), key); } bool MMKV::set(float value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(float value, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); size_t size = mmkv_unlikely(m_enableKeyExpire) ? Fixed32Size + pbFloatSize() : pbFloatSize(); MMBuffer data(size); CodedOutputData output(data.getPtr(), size); output.writeFloat(value); if (mmkv_unlikely(m_enableKeyExpire)) { auto time = (expireDuration != ExpireNever) ? safeExpirationPlusCurrentTime(expireDuration) : ExpireNever; output.writeRawLittleEndian32(UInt32ToInt32(time)); } else { assert(expireDuration == ExpireNever && "setting expire duration without calling enableAutoKeyExpire() first"); } return setDataForKey(std::move(data), key); } bool MMKV::set(double value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(double value, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); size_t size = mmkv_unlikely(m_enableKeyExpire) ? Fixed32Size + pbDoubleSize() : pbDoubleSize(); MMBuffer data(size); CodedOutputData output(data.getPtr(), size); output.writeDouble(value); if (mmkv_unlikely(m_enableKeyExpire)) { auto time = (expireDuration != ExpireNever) ? safeExpirationPlusCurrentTime(expireDuration) : ExpireNever; output.writeRawLittleEndian32(UInt32ToInt32(time)); } else { assert(expireDuration == ExpireNever && "setting expire duration without calling enableAutoKeyExpire() first"); } return setDataForKey(std::move(data), key); } bool MMKV::setDataForKey(mmkv::MMBuffer &&data, MMKV::MMKVKey_t key, uint32_t expireDuration, bool isDataHolder) { if (!isDataHolder && data.length() == 0) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); if (mmkv_likely(!m_enableKeyExpire)) { assert(expireDuration == ExpireNever && "setting expire duration without calling enableAutoKeyExpire() first"); return setDataForKey(std::move(data), key, isDataHolder); } else { if (data.length() > numeric_limits::max()) { MMKVError("[%s] reject value too large to encode: %zu", m_mmapID.c_str(), data.length()); return false; } auto dataLength = static_cast(data.length()); uint64_t encodedLength = static_cast(dataLength) + (isDataHolder ? pbRawVarint32Size(dataLength) : 0) + Fixed32Size; if (encodedLength > numeric_limits::max()) { MMKVError("[%s] reject expiring value too large to encode: %zu", m_mmapID.c_str(), data.length()); return false; } auto tmp = MMBuffer(static_cast(encodedLength)); CodedOutputData output(tmp.getPtr(), tmp.length()); if (isDataHolder) { output.writeData(data); } else { output.writeRawData(data); } auto time = (expireDuration != ExpireNever) ? safeExpirationPlusCurrentTime(expireDuration) : ExpireNever; output.writeRawLittleEndian32(UInt32ToInt32(time)); return setDataForKey(std::move(tmp), key); } } bool MMKV::set(const char *value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(const char *value, MMKVKey_t key, uint32_t expireDuration) { if (!value) { removeValueForKey(key); return true; } return setDataForKey(MMBuffer((void *) value, strlen(value), MMBufferNoCopy), key, expireDuration); } bool MMKV::set(const string &value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(const string &value, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } return setDataForKey(MMBuffer((void *) value.data(), value.length(), MMBufferNoCopy), key, expireDuration); } bool MMKV::set(string_view value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(string_view value, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } return setDataForKey(MMBuffer((void *) value.data(), value.length(), MMBufferNoCopy), key, expireDuration); } bool MMKV::set(const MMBuffer &value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(const MMBuffer &value, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } return setDataForKey(MMBuffer(value.getPtr(), value.length(), MMBufferNoCopy), key, expireDuration); } bool MMKV::set(const vector &value, MMKVKey_t key) { return set(value, key, m_expiredInSeconds); } bool MMKV::set(const vector &v, MMKVKey_t key, uint32_t expireDuration) { if (isKeyEmpty(key)) { return false; } #ifdef MMKV_HAS_CPP20 auto data = MiniPBCoder::encodeDataWithObject(std::span(v)); #else auto data = MiniPBCoder::encodeDataWithObject(v); #endif return setDataForKey(std::move(data), key, expireDuration, false); } bool MMKV::getString(MMKVKey_t key, string &result, bool inplaceModification) { if (isKeyEmpty(key)) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { CodedInputData input(data.getPtr(), data.length()); if (inplaceModification) { input.readString(result); } else { result = input.readString(); } return true; } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } return false; } bool MMKV::getBytes(MMKVKey_t key, mmkv::MMBuffer &result) { if (isKeyEmpty(key)) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { CodedInputData input(data.getPtr(), data.length()); result = input.readData(); return true; } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } return false; } MMBuffer MMKV::getBytes(MMKVKey_t key) { if (isKeyEmpty(key)) { return MMBuffer(); } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { CodedInputData input(data.getPtr(), data.length()); return input.readData(); } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } return MMBuffer(); } bool MMKV::getVector(MMKVKey_t key, vector &result) { if (isKeyEmpty(key)) { return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { result = MiniPBCoder::decodeVector(data); return true; } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } return false; } void MMKV::shared_lock() { m_lock->lock(); m_sharedProcessLock->lock(); } void MMKV::shared_unlock() { m_sharedProcessLock->unlock(); m_lock->unlock(); } bool MMKV::getBool(MMKVKey_t key, bool defaultValue, bool *hasValue) { if (isKeyEmpty(key)) { if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { CodedInputData input(data.getPtr(), data.length()); if (hasValue != nullptr) { *hasValue = true; } return input.readBool(); } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } int32_t MMKV::getInt32(MMKVKey_t key, int32_t defaultValue, bool *hasValue) { if (isKeyEmpty(key)) { if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { CodedInputData input(data.getPtr(), data.length()); if (hasValue != nullptr) { *hasValue = true; } return input.readInt32(); } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } uint32_t MMKV::getUInt32(MMKVKey_t key, uint32_t defaultValue, bool *hasValue) { if (isKeyEmpty(key)) { if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { CodedInputData input(data.getPtr(), data.length()); if (hasValue != nullptr) { *hasValue = true; } return input.readUInt32(); } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } int64_t MMKV::getInt64(MMKVKey_t key, int64_t defaultValue, bool *hasValue) { if (isKeyEmpty(key)) { if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { CodedInputData input(data.getPtr(), data.length()); if (hasValue != nullptr) { *hasValue = true; } return input.readInt64(); } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } uint64_t MMKV::getUInt64(MMKVKey_t key, uint64_t defaultValue, bool *hasValue) { if (isKeyEmpty(key)) { if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { CodedInputData input(data.getPtr(), data.length()); if (hasValue != nullptr) { *hasValue = true; } return input.readUInt64(); } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } float MMKV::getFloat(MMKVKey_t key, float defaultValue, bool *hasValue) { if (isKeyEmpty(key)) { if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { CodedInputData input(data.getPtr(), data.length()); if (hasValue != nullptr) { *hasValue = true; } return input.readFloat(); } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } double MMKV::getDouble(MMKVKey_t key, double defaultValue, bool *hasValue) { if (isKeyEmpty(key)) { if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (data.length() > 0) { try { CodedInputData input(data.getPtr(), data.length()); if (hasValue != nullptr) { *hasValue = true; } return input.readDouble(); } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } if (hasValue != nullptr) { *hasValue = false; } return defaultValue; } size_t MMKV::getValueSize(MMKVKey_t key, bool actualSize) { if (isKeyEmpty(key)) { return 0; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); if (actualSize) { try { CodedInputData input(data.getPtr(), data.length()); auto length = input.readUInt32(); auto s_length = static_cast(length); if (pbRawVarint32Size(length) + s_length == data.length()) { return s_length; } } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("decode fail"); } } return data.length(); } int32_t MMKV::writeValueToBuffer(MMKVKey_t key, void *ptr, int32_t size) { if (isKeyEmpty(key) || size < 0) { return -1; } auto s_size = static_cast(size); SCOPED_LOCK(m_lock); SCOPED_LOCK(m_sharedProcessLock); auto data = getDataForKey(key); try { CodedInputData input(data.getPtr(), data.length()); auto length = input.readInt32(); auto offset = pbRawVarint32Size(length); if (length >= 0) { auto s_length = static_cast(length); if (offset + s_length == data.length()) { if (s_length <= s_size) { memcpy(ptr, (uint8_t *) data.getPtr() + offset, s_length); return length; } } else { if (data.length() <= s_size) { memcpy(ptr, data.getPtr(), data.length()); return static_cast(data.length()); } } } } catch (std::exception &exception) { MMKVError("%s", exception.what()); } catch (...) { MMKVError("encode fail"); } return -1; } // enumerate bool MMKV::containsKey(MMKVKey_t key) { SCOPED_LOCK(m_lock); checkLoadData(); if (mmkv_likely(!m_enableKeyExpire)) { if (m_crypter) { return m_dicCrypt->find(key) != m_dicCrypt->end(); } else { return m_dic->find(key) != m_dic->end(); } } auto raw = getDataWithoutMTimeForKey(key); return raw.length() != 0; } size_t MMKV::count(bool filterExpire) { SCOPED_LOCK(m_lock); checkLoadData(); if (mmkv_unlikely(filterExpire && m_enableKeyExpire)) { if (isReadOnly()) { filterExpiredKeys(); } else { SCOPED_LOCK(m_exclusiveProcessLock); fullWriteback(nullptr, true); } } if (m_crypter) { return m_dicCrypt->size(); } else { return m_dic->size(); } } size_t MMKV::totalSize() { SCOPED_LOCK(m_lock); checkLoadData(); return m_file->getFileSize(); } size_t MMKV::actualSize() { SCOPED_LOCK(m_lock); checkLoadData(); return m_actualSize; } bool MMKV::removeValueForKey(MMKVKey_t key) { if (isKeyEmpty(key)) { return false; } if (isReadOnly()) { MMKVWarning("[%s] file readonly", m_mmapID.c_str()); return false; } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); return removeDataForKey(key); } #ifndef MMKV_APPLE vector MMKV::allKeys(bool filterExpire) { SCOPED_LOCK(m_lock); checkLoadData(); if (mmkv_unlikely(filterExpire && m_enableKeyExpire)) { if (isReadOnly()) { filterExpiredKeys(); } else { SCOPED_LOCK(m_exclusiveProcessLock); fullWriteback(nullptr, true); } } vector keys; if (m_crypter) { for (const auto &itr : *m_dicCrypt) { keys.push_back(itr.first); } } else { for (const auto &itr : *m_dic) { keys.push_back(itr.first); } } return keys; } bool MMKV::removeValuesForKeys(const vector &arrKeys) { if (isReadOnly()) { MMKVWarning("[%s] file readonly", m_mmapID.c_str()); return false; } if (arrKeys.empty()) { return true; } if (arrKeys.size() == 1) { return removeValueForKey(arrKeys[0]); } SCOPED_LOCK(m_lock); SCOPED_LOCK(m_exclusiveProcessLock); checkLoadData(); size_t deleteCount = 0; if (m_crypter) { for (const auto &key : arrKeys) { auto itr = m_dicCrypt->find(key); if (itr != m_dicCrypt->end()) { m_dicCrypt->erase(itr); deleteCount++; } } } else { for (const auto &key : arrKeys) { auto itr = m_dic->find(key); if (itr != m_dic->end()) { m_dic->erase(itr); deleteCount++; } } } if (deleteCount > 0) { m_hasFullWriteback = false; auto ret = fullWriteback(); if (!ret) { clearMemoryCache(); } return ret; } return true; } #endif // MMKV_APPLE // file void MMKV::sync(SyncFlag flag) { MMKVInfo("MMKV::sync, SyncFlag = %d", flag); SCOPED_LOCK(m_lock); if (m_needLoadFromFile || !isFileValid()) { return; } SCOPED_LOCK(m_exclusiveProcessLock); m_file->msync(flag); m_metaFile->msync(flag); } void MMKV::lock() { SCOPED_LOCK(m_lock); m_exclusiveProcessLock->lock(); } void MMKV::unlock() { SCOPED_LOCK(m_lock); m_exclusiveProcessLock->unlock(); } bool MMKV::try_lock() { SCOPED_LOCK(m_lock); return m_exclusiveProcessLock->try_lock(); } #ifndef MMKV_WIN32 void MMKV::lock_thread() { m_lock->lock(); } void MMKV::unlock_thread() { m_lock->unlock(); } bool MMKV::try_lock_thread() { return m_lock->try_lock(); } #endif // backup static bool sameDirectory(const MMKVPath_t &left, const MMKVPath_t &right) { if (left == right) { return true; } error_code error; return fs::equivalent(fs::path(left), fs::path(right), error) && !error; } static bool backupOneToDirectoryByFilePath(const string &mmapKey, const MMKVPath_t &srcPath, const MMKVPath_t &dstPath) { auto srcCRCPath = srcPath + CRC_SUFFIX; File crcFile(srcCRCPath, OpenFlag::ReadOnly); if (!crcFile.isFileValid()) { return false; } bool ret; { const auto &srcUTF8Path = MMKVPath_t2String(srcPath); const auto &dstUTF8Path = MMKVPath_t2String(dstPath); MMKVInfo("backup one mmkv[%s] from [%s] to [%s]", mmapKey.c_str(), srcUTF8Path.c_str(), dstUTF8Path.c_str()); FileLock fileLock(crcFile.getFd()); InterProcessLock lock(&fileLock, SharedLockType); SCOPED_LOCK(&lock); ret = copyFile(srcPath, dstPath); if (ret) { auto dstCRCPath = dstPath + CRC_SUFFIX; ret = copyFile(srcCRCPath, dstCRCPath); } MMKVInfo("finish backup one mmkv[%s]", mmapKey.c_str()); } return ret; } bool MMKV::backupOneToDirectory(const string &mmapKey, const MMKVPath_t &dstPath, const MMKVPath_t &srcPath, bool compareFullPath) { if (!g_instanceLock) { return false; } // we have to lock the creation of MMKV instance, regardless of in cache or not SCOPED_LOCK(g_instanceLock); MMKV *kv = nullptr; if (!compareFullPath) { auto itr = g_instanceDic->find(mmapKey); if (itr != g_instanceDic->end()) { kv = itr->second; } } else { // mmapKey is actually filename, we can't simply call find() for (auto &pair : *g_instanceDic) { if (pair.second->m_path == srcPath) { kv = pair.second; break; } } } // get one in cache, do it the easy way if (kv) { const auto &srcUTF8Path = MMKVPath_t2String(srcPath); const auto &dstUTF8Path = MMKVPath_t2String(dstPath); MMKVInfo("backup one cached mmkv[%s] from [%s] to [%s]", mmapKey.c_str(), srcUTF8Path.c_str(), dstUTF8Path.c_str()); SCOPED_LOCK(kv->m_lock); SCOPED_LOCK(kv->m_sharedProcessLock); kv->sync(); auto ret = copyFile(kv->m_path, dstPath); if (ret) { auto dstCRCPath = dstPath + CRC_SUFFIX; ret = copyFile(kv->m_crcPath, dstCRCPath); } MMKVInfo("finish backup one mmkv[%s], ret: %d", mmapKey.c_str(), ret); return ret; } // no luck with cache, do it the hard way bool ret = backupOneToDirectoryByFilePath(mmapKey, srcPath, dstPath); return ret; } bool MMKV::backupOneToDirectory(const string &mmapID, const MMKVPath_t &dstDir, const MMKVPath_t *srcDir) { auto rootPath = srcDir ? srcDir : &g_realRootDir; if (sameDirectory(*rootPath, dstDir)) { return true; } mkPath(dstDir); auto dstPath = mappedKVPathWithID(mmapID, &dstDir); auto ns = nameSpace(*rootPath); rootPath = &ns.getRootDir(); string mmapKey = mmapedKVKey(mmapID, rootPath, true); #ifdef MMKV_ANDROID string srcPath; switch (tryMigrateLegacyMMKVFile(mmapID, rootPath, true)) { case MigrateStatus::OldToNewMigrateFail: { auto legacyID = legacyMmapedKVKey(mmapID, rootPath); srcPath = mappedKVPathWithID(legacyID, rootPath, MMKV_MULTI_PROCESS, true); break; } case MigrateStatus::NoneExist: MMKVWarning("file with ID [%s] not exist in path [%s]", mmapID.c_str(), rootPath->c_str()); return false; default: srcPath = mappedKVPathWithID(mmapID, rootPath, MMKV_MULTI_PROCESS, true); break; } #else auto srcPath = mappedKVPathWithID(mmapID, rootPath, true); #endif return backupOneToDirectory(mmapKey, dstPath, srcPath, false); } MMKVPath_t filename(const MMKVPath_t &path) { auto startPos = path.rfind(MMKV_PATH_SLASH); startPos++; // don't need to check for npos, because npos+1 == 0 auto filename = path.substr(startPos); return filename; } size_t MMKV::backupAllToDirectory(const MMKVPath_t &dstDir, const MMKVPath_t &srcDir, bool isInSpecialDir) { unordered_set fileSet; walkInDir(srcDir, WalkFile, [&](const MMKVPath_t &filePath, WalkType) { fileSet.insert(filePath); }); size_t count = 0; if (!fileSet.empty()) { mkPath(dstDir); auto compareFullPath = isInSpecialDir; for (auto &srcPath : fileSet) { auto srcCRCPath = srcPath + CRC_SUFFIX; if (fileSet.find(srcCRCPath) == fileSet.end()) { continue; } auto basename = filename(srcPath); const auto &strBasename = MMKVPath_t2String(basename); auto mmapKey = isInSpecialDir ? strBasename : mmapedKVKey(strBasename, &srcDir); auto dstPath = dstDir + MMKV_PATH_SLASH; dstPath += basename; if (backupOneToDirectory(mmapKey, dstPath, srcPath, compareFullPath)) { count++; } } } return count; } size_t MMKV::backupAllToDirectory(const MMKVPath_t &dstDir, const MMKVPath_t *srcDir) { auto rootPath = srcDir ? srcDir : &g_realRootDir; if (sameDirectory(*rootPath, dstDir)) { return true; } auto count = backupAllToDirectory(dstDir, *rootPath, false); auto specialSrcDir = *rootPath + MMKV_PATH_SLASH + SPECIAL_CHARACTER_DIRECTORY_NAME; if (isFileExist(specialSrcDir)) { auto specialDstDir = dstDir + MMKV_PATH_SLASH + SPECIAL_CHARACTER_DIRECTORY_NAME; count += backupAllToDirectory(specialDstDir, specialSrcDir, true); } return count; } // restore static bool isValidRestoreSource(const MMKVPath_t &srcPath) { File srcFile(srcPath, OpenFlag::ReadOnly); File srcCRCFile(srcPath + CRC_SUFFIX, OpenFlag::ReadOnly); return srcFile.isFileValid() && srcFile.getActualFileSize() >= Fixed32Size && srcCRCFile.isFileValid() && srcCRCFile.getActualFileSize() >= sizeof(MMKVMetaInfo); } static bool restoreOneFromDirectoryByFilePath(const string &mmapKey, const MMKVPath_t &srcPath, const MMKVPath_t &dstPath) { auto dstCRCPath = dstPath + CRC_SUFFIX; File dstCRCFile(std::move(dstCRCPath), OpenFlag::ReadWrite | OpenFlag::Create); if (!dstCRCFile.isFileValid()) { return false; } bool ret; { const auto &srcUTF8Path = MMKVPath_t2String(srcPath); const auto &dstUTF8Path = MMKVPath_t2String(dstPath); MMKVInfo("restore one mmkv[%s] from [%s] to [%s]", mmapKey.c_str(), srcUTF8Path.c_str(), dstUTF8Path.c_str()); FileLock fileLock(dstCRCFile.getFd()); InterProcessLock lock(&fileLock, ExclusiveLockType); SCOPED_LOCK(&lock); ret = copyFileContent(srcPath, dstPath); if (ret) { auto srcCRCPath = srcPath + CRC_SUFFIX; ret = copyFileContent(srcCRCPath, dstCRCFile.getFd()); } MMKVInfo("finish restore one mmkv[%s]", mmapKey.c_str()); } return ret; } // We can't simply replace the existing file, because other processes might have already open it. // They won't know a difference when the file has been replaced. // We have to let them know by overriding the existing file with new content. bool MMKV::restoreOneFromDirectory(const string &mmapKey, const MMKVPath_t &srcPath, const MMKVPath_t &dstPath, bool compareFullPath) { if (!g_instanceLock || !isValidRestoreSource(srcPath)) { return false; } // we have to lock the creation of MMKV instance, regardless of in cache or not SCOPED_LOCK(g_instanceLock); MMKV *kv = nullptr; if (!compareFullPath) { auto itr = g_instanceDic->find(mmapKey); if (itr != g_instanceDic->end()) { kv = itr->second; } } else { // mmapKey is actually filename, we can't simply call find() for (auto &pair : *g_instanceDic) { if (pair.second->m_path == dstPath) { kv = pair.second; break; } } } // get one in cache, do it the easy way if (kv) { const auto &srcUTF8Path = MMKVPath_t2String(srcPath); const auto &dstUTF8Path = MMKVPath_t2String(dstPath); MMKVInfo("restore one cached mmkv[%s] from [%s] to [%s]", mmapKey.c_str(), srcUTF8Path.c_str(), dstUTF8Path.c_str()); auto srcCRCPath = srcPath + CRC_SUFFIX; #ifndef MMKV_ANDROID MemoryFile srcCRCFile(srcCRCPath, 0, true); #else MemoryFile srcCRCFile(srcCRCPath, MMFILE_TYPE_FILE, 0, true); #endif if (!srcCRCFile.isFileValid() || srcCRCFile.getFileSize() < sizeof(MMKVMetaInfo)) { return false; } SCOPED_LOCK(kv->m_lock); SCOPED_LOCK(kv->m_exclusiveProcessLock); if (kv->isReadOnly() || !kv->m_metaFile->isFileValid()) { return false; } kv->sync(); auto ret = copyFileContent(srcPath, kv->m_file->getFd()); kv->m_file->cleanMayflyFD(); if (ret) { memcpy(kv->m_metaFile->getMemory(), srcCRCFile.getMemory(), sizeof(MMKVMetaInfo)); } // reload data after restore kv->clearMemoryCache(); // A still-lazy instance skips clearMemoryCache(), but its mapping may have changed size. kv->m_file->clearMemoryCache(); kv->loadFromFile(); if (kv->isMultiProcess()) { kv->notifyContentChanged(); } MMKVInfo("finish restore one mmkv[%s], ret: %d", mmapKey.c_str(), ret); return ret; } // no luck with cache, do it the hard way bool ret = restoreOneFromDirectoryByFilePath(mmapKey, srcPath, dstPath); return ret; } bool MMKV::restoreOneFromDirectory(const string &mmapID, const MMKVPath_t &srcDir, const MMKVPath_t *dstDir) { auto rootPath = dstDir ? dstDir : &g_realRootDir; if (sameDirectory(*rootPath, srcDir)) { return true; } mkPath(*rootPath); auto ns = nameSpace(*rootPath); rootPath = &ns.getRootDir(); auto mmapKey = mmapedKVKey(mmapID, rootPath, true); #ifdef MMKV_ANDROID auto srcPath = mappedKVPathWithID(mmapID, &srcDir, MMKV_MULTI_PROCESS, true); string dstPath; if (tryMigrateLegacyMMKVFile(mmapID, rootPath, true) == MigrateStatus::OldToNewMigrateFail) { auto legacyID = legacyMmapedKVKey(mmapID, rootPath); dstPath = mappedKVPathWithID(legacyID, rootPath, MMKV_MULTI_PROCESS, true); } else { dstPath = mappedKVPathWithID(mmapID, rootPath, MMKV_MULTI_PROCESS, true); } #else auto srcPath = mappedKVPathWithID(mmapID, &srcDir, true); auto dstPath = mappedKVPathWithID(mmapID, rootPath, true); #endif return restoreOneFromDirectory(mmapKey, srcPath, dstPath, false); } size_t MMKV::restoreAllFromDirectory(const MMKVPath_t &srcDir, const MMKVPath_t &dstDir, bool isInSpecialDir) { unordered_set fileSet; walkInDir(srcDir, WalkFile, [&](const MMKVPath_t &filePath, WalkType) { fileSet.insert(filePath); }); size_t count = 0; if (!fileSet.empty()) { mkPath(dstDir); auto compareFullPath = isInSpecialDir; for (auto &srcPath : fileSet) { auto srcCRCPath = srcPath + CRC_SUFFIX; if (fileSet.find(srcCRCPath) == fileSet.end()) { continue; } auto basename = filename(srcPath); const auto &strBasename = MMKVPath_t2String(basename); auto mmapKey = isInSpecialDir ? strBasename : mmapedKVKey(strBasename, &dstDir); auto dstPath = dstDir + MMKV_PATH_SLASH; dstPath += basename; if (restoreOneFromDirectory(mmapKey, srcPath, dstPath, compareFullPath)) { count++; } } } return count; } size_t MMKV::restoreAllFromDirectory(const MMKVPath_t &srcDir, const MMKVPath_t *dstDir) { auto rootPath = dstDir ? dstDir : &g_realRootDir; if (sameDirectory(*rootPath, srcDir)) { return true; } auto count = restoreAllFromDirectory(srcDir, *rootPath, true); auto specialSrcDir = srcDir + MMKV_PATH_SLASH + SPECIAL_CHARACTER_DIRECTORY_NAME; if (isFileExist(specialSrcDir)) { auto specialDstDir = *rootPath + MMKV_PATH_SLASH + SPECIAL_CHARACTER_DIRECTORY_NAME; count += restoreAllFromDirectory(specialSrcDir, specialDstDir, true); } return count; } // callbacks void MMKV::registerHandler(mmkv::MMKVHandler *handler) { if (!g_instanceLock) { return; } SCOPED_LOCK(g_instanceLock); g_handler = handler; } void MMKV::unRegisterHandler() { if (!g_instanceLock) { return; } SCOPED_LOCK(g_instanceLock); g_handler = nullptr; } void MMKV::setLogLevel(MMKVLogLevel level) { if (!g_instanceLock) { return; } SCOPED_LOCK(g_instanceLock); g_currentLogLevel = level; } static void mkSpecialCharacterFileDirectory() { MMKVPath_t path = g_realRootDir + MMKV_PATH_SLASH + SPECIAL_CHARACTER_DIRECTORY_NAME; mkPath(path); } template static string md5(const basic_string &value) { uint8_t md[MD5_DIGEST_LENGTH] = {}; char tmp[3] = {}, buf[33] = {}; openssl::MD5((const uint8_t *) value.c_str(), value.size() * (sizeof(T) / sizeof(uint8_t)), md); for (auto ch : md) { snprintf(tmp, sizeof(tmp), "%2.2x", ch); strcat(buf, tmp); } return {buf}; } static MMKVPath_t encodeFilePath(const string &mmapID) { const char *specialCharacters = "\\/:*?\"<>|"; string encodedID; bool hasSpecialCharacter = false; for (auto ch : mmapID) { if (strchr(specialCharacters, ch) != nullptr) { encodedID = md5(mmapID); hasSpecialCharacter = true; break; } } if (hasSpecialCharacter) { static ThreadOnceToken_t once = ThreadOnceUninitialized; ThreadLock::ThreadOnce(&once, mkSpecialCharacterFileDirectory); return MMKVPath_t(SPECIAL_CHARACTER_DIRECTORY_NAME) + MMKV_PATH_SLASH + string2MMKVPath_t(encodedID); } else { return string2MMKVPath_t(mmapID); } } static MMKVPath_t encodeFilePath(const string &mmapID, const MMKVPath_t &rootDir) { const char *specialCharacters = "\\/:*?\"<>|"; string encodedID; bool hasSpecialCharacter = false; for (auto ch : mmapID) { if (strchr(specialCharacters, ch) != nullptr) { encodedID = md5(mmapID); hasSpecialCharacter = true; break; } } if (hasSpecialCharacter) { MMKVPath_t path = rootDir + MMKV_PATH_SLASH + SPECIAL_CHARACTER_DIRECTORY_NAME; mkPath(path); return MMKVPath_t(SPECIAL_CHARACTER_DIRECTORY_NAME) + MMKV_PATH_SLASH + string2MMKVPath_t(encodedID); } else { return string2MMKVPath_t(mmapID); } } string mmapedKVKey(const string &mmapID, const MMKVPath_t *rootPath, bool alreadyAbsolute) { MMKVPath_t path; // compare by pointer to speedup a bit, it's OK false detecting if (rootPath && (rootPath != &g_realRootDir)) { auto tmp = *rootPath + MMKV_PATH_SLASH + string2MMKVPath_t(mmapID); if (alreadyAbsolute) { path = std::move(tmp); } else { path = absolutePath(tmp); } } else { path = g_realRootDir + MMKV_PATH_SLASH + string2MMKVPath_t(mmapID); } return md5(path); } string legacyMmapedKVKey(const string &mmapID, const MMKVPath_t *rootPath) { if (rootPath && (*rootPath != g_rootDir)) { return md5(*rootPath + MMKV_PATH_SLASH + string2MMKVPath_t(mmapID)); } return mmapID; } #ifndef MMKV_ANDROID MMKVPath_t mappedKVPathWithID(const string &mmapID, const MMKVPath_t *rootPath, bool alreadyAbsolute) { if (rootPath && (rootPath != &g_realRootDir)) { auto path = *rootPath + MMKV_PATH_SLASH + encodeFilePath(mmapID, *rootPath); if (alreadyAbsolute) { return path; } else { return absolutePath(path); } } auto path = g_realRootDir + MMKV_PATH_SLASH + encodeFilePath(mmapID); return path; } #else MMKVPath_t mappedKVPathWithID(const string &mmapID, const MMKVPath_t *rootPath, MMKVMode mode, bool alreadyAbsolute) { if (mode & MMKV_ASHMEM) { return ashmemMMKVPathWithID(encodeFilePath(mmapID)); } else if (rootPath && (rootPath != &g_realRootDir)) { auto path = *rootPath + MMKV_PATH_SLASH + encodeFilePath(mmapID, *rootPath); if (alreadyAbsolute) { return path; } else { return absolutePath(path); } } auto path = g_realRootDir + MMKV_PATH_SLASH + encodeFilePath(mmapID); return path; } #endif MMKVPath_t crcPathWithPath(const MMKVPath_t &kvPath) { return kvPath + CRC_SUFFIX; } MMKVRecoverStrategic onMMKVCRCCheckFail(const string &mmapID) { if (g_handler) { return g_handler->onMMKVCRCCheckFail(mmapID); } return OnErrorDiscard; } MMKVRecoverStrategic onMMKVFileLengthError(const string &mmapID) { if (g_handler) { return g_handler->onMMKVFileLengthError(mmapID); } return OnErrorDiscard; } // NameSpace NameSpace MMKV::nameSpace(const MMKVPath_t &rootDir) { if (!g_instanceLock) { ensureMinimalInitialize(); } static ThreadOnceToken_t once = ThreadOnceUninitialized; ThreadLock::ThreadOnce(&once, []{ g_namespaceLock = new ThreadLock; g_namespaceLock->initialize(); }); SCOPED_LOCK(g_namespaceLock); auto itr = g_realRootMap.find(rootDir); if (itr == g_realRootMap.end()) { auto realRoot = absolutePath(rootDir); if (realRoot.ends_with(MMKV_PATH_SLASH)) { realRoot.erase(realRoot.size() - 1); } itr = g_realRootMap.emplace(rootDir, realRoot).first; } return NameSpace(itr->second); } NameSpace MMKV::defaultNameSpace() { if (g_rootDir.empty()) { MMKVWarning("MMKV has not been initialized, there's no default NameSpace."); return NameSpace(MMKVPath_t()); } return NameSpace(g_realRootDir); } MMKV *NameSpace::mmkvWithID(const string &mmapID, MMKVMode mode, const string *cryptKey, size_t expectedCapacity, bool aes256) { MMKVConfig config; config.mode = mode; #ifndef MMKV_DISABLE_CRYPT config.aes256 = aes256; config.cryptKey = cryptKey; #endif config.rootPath = &m_rootDir; config.expectedCapacity = expectedCapacity; return MMKV::getMMKVWithID(mmapID, config); } MMKV *NameSpace::mmkvWithID(const string &mmapID, const MMKVConfig &config) { if (!config.rootPath || *config.rootPath != m_rootDir) { auto newConfig = config; newConfig.rootPath = &m_rootDir; return MMKV::getMMKVWithID(mmapID, newConfig); } return MMKV::getMMKVWithID(mmapID, config); } bool NameSpace::backupOneToDirectory(const std::string &mmapID, const MMKVPath_t &dstDir) { return MMKV::backupOneToDirectory(mmapID, dstDir, &m_rootDir); } bool NameSpace::restoreOneFromDirectory(const std::string &mmapID, const MMKVPath_t &srcDir) { return MMKV::restoreOneFromDirectory(mmapID, srcDir, &m_rootDir); } size_t NameSpace::backupAllToDirectory(const MMKVPath_t &dstDir) { return MMKV::backupAllToDirectory(dstDir, &m_rootDir); } size_t NameSpace::restoreAllFromDirectory(const MMKVPath_t &srcDir) { return MMKV::restoreAllFromDirectory(srcDir, &m_rootDir); } bool NameSpace::isFileValid(const std::string &mmapID) { return MMKV::isFileValid(mmapID, &m_rootDir); } bool NameSpace::removeStorage(const std::string &mmapID) { return MMKV::removeStorage(mmapID, &m_rootDir); } bool NameSpace::checkExist(const std::string &mmapID) { return MMKV::checkExist(mmapID, &m_rootDir); } MMKV_NAMESPACE_END