Skip to content

Commit

Permalink
add readonly support
Browse files Browse the repository at this point in the history
  • Loading branch information
lingol committed Sep 6, 2024
1 parent 3624d0e commit 47d3c08
Show file tree
Hide file tree
Showing 52 changed files with 634 additions and 123 deletions.
16 changes: 16 additions & 0 deletions Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<MMKV *>(handle);
if (kv) {
return kv->isMultiProcess();
}
return false;
}

MMKV_EXPORT bool isReadOnly(void *handle) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
return kv->isReadOnly();
}
return false;
}

#endif // MMKV_DISABLE_FLUTTER
18 changes: 18 additions & 0 deletions Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = {
Expand Down Expand Up @@ -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) {
Expand Down
15 changes: 15 additions & 0 deletions Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKV.java
Original file line number Diff line number Diff line change
Expand Up @@ -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).
*
Expand Down Expand Up @@ -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;

Expand Down
6 changes: 3 additions & 3 deletions Android/MMKV/mmkvdemo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ public void onClick(View v) {
// testFastNativeSpeed();
testRemoveStorage();
overrideTest();
testReadOnly();
}

private void testCompareBeforeSet() {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
}
34 changes: 22 additions & 12 deletions Core/MMKV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t>(DEFAULT_MMAP_SIZE, roundUp<size_t>(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;

Expand All @@ -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
/*{
Expand Down Expand Up @@ -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
Expand All @@ -1129,9 +1134,13 @@ vector<string> MMKV::allKeys(bool filterExpire) {
return keys;
}

void MMKV::removeValuesForKeys(const vector<string> &arrKeys) {
bool MMKV::removeValuesForKeys(const vector<string> &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]);
Expand Down Expand Up @@ -1162,8 +1171,9 @@ void MMKV::removeValuesForKeys(const vector<string> &arrKeys) {
if (deleteCount > 0) {
m_hasFullWriteback = false;

fullWriteback();
return fullWriteback();
}
return true;
}

#endif // MMKV_APPLE
Expand Down Expand Up @@ -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();
}

Expand Down
24 changes: 19 additions & 5 deletions Core/MMKV.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<MMKVMode>(static_cast<uint32_t>(one) | static_cast<uint32_t>(other));
}

#define MMKV_OUT

#ifdef MMKV_HAS_CPP20
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -441,10 +455,10 @@ class MMKV {
// filterExpire: return all non-expired keys, keep in mind it comes with cost
std::vector<std::string> allKeys(bool filterExpire = false);

void removeValuesForKeys(const std::vector<std::string> &arrKeys);
bool removeValuesForKeys(const std::vector<std::string> &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);
Expand Down
22 changes: 11 additions & 11 deletions Core/MMKV_Android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t>(DEFAULT_MMAP_SIZE, roundUp<size_t>(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;

Expand All @@ -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
/*{
Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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
/*{
Expand Down Expand Up @@ -213,7 +213,7 @@ bool MMKV::checkProcessMode() {
return true;
}

if (m_isInterProcess) {
if (isMultiProcess()) {
if (!m_exclusiveProcessModeLock) {
m_exclusiveProcessModeLock = new InterProcessLock(m_fileModeLock, ExclusiveLockType);
}
Expand Down
Loading

0 comments on commit 47d3c08

Please sign in to comment.