implement pure go version

This commit is contained in:
Unlock Music Dev 2022-12-05 06:27:41 +08:00
parent a40e4e5b9e
commit 629b839482
Signed by: um-dev
GPG Key ID: 95202E10D3413A1D
18 changed files with 332 additions and 1339 deletions

View File

@ -1,119 +0,0 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2020 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.
*/
// MMKV is a cross-platform key-value storage framework developed by WeChat.
package mmkv
/*
#include "golang-bridge.h"
#include <stdlib.h>
*/
import "C"
// the callback type of Logger
type LogHandler func(level int, file string, line int, function string, message string)
var gLogHandler LogHandler
// register a log callback
func RegisterLogHandler(logHandler LogHandler) {
gLogHandler = logHandler
C.setWantsLogRedirect(C.bool(true))
}
// unregister a log callback
func UnRegisterLogHandler() {
gLogHandler = nil
C.setWantsLogRedirect(C.bool(false))
}
//export myLogHandler
func myLogHandler(level int, file string, line int, function string, message string) {
if gLogHandler != nil {
gLogHandler(level, file, line, function, message)
}
}
const (
OnErrorDiscard = iota // When there's an error, MMKV will discard everything by default.
OnErrorRecover // When there's an error, MMKV will try to recover as much data as possible.
)
const (
MMKVCRCCheckFail = iota
MMKVFileLength
)
// the callback type of error handler
// error is either MMKVCRCCheckFail or MMKVFileLength
// return OnErrorDiscard (default) or OnErrorRecover
type ErrorHandler func(mmapID string, error int) int
var gErrorHandler ErrorHandler
// register a error callback
func RegisterErrorHandler(errorHandler ErrorHandler) {
gErrorHandler = errorHandler
C.setWantsErrorHandle(C.bool(true))
}
// unregister a error callback
func UnRegisterErrorHandler() {
gErrorHandler = nil
C.setWantsErrorHandle(C.bool(false))
}
//export myErrorHandler
func myErrorHandler(mmapID string, error int) int {
if gErrorHandler != nil {
return gErrorHandler(mmapID, error)
}
return OnErrorDiscard
}
// the type of content change handler
type ContentChangeHandler func(mmapID string)
var gContentChangeHandler ContentChangeHandler
// register a content change callback
func RegisterContentChangeHandler(contentChangeHandler ContentChangeHandler) {
gContentChangeHandler = contentChangeHandler
C.setWantsContentChangeHandle(C.bool(true))
}
// unregister a content change callback
func UnRegisterContentChangeHandler() {
gContentChangeHandler = nil
C.setWantsContentChangeHandle(C.bool(false))
}
//export myContentChangeHandler
func myContentChangeHandler(mmapID string) {
if gContentChangeHandler != nil {
gContentChangeHandler(mmapID)
}
}

15
go.mod
View File

@ -1,3 +1,16 @@
module unlock-music.dev/mmkv module unlock-music.dev/mmkv
go 1.15 go 1.19
require (
github.com/golang/protobuf v1.5.0
github.com/stretchr/testify v1.8.1
golang.org/x/exp v0.0.0-20221204150635-6dcec336b2bb
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

27
go.sum Normal file
View File

@ -0,0 +1,27 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/exp v0.0.0-20221204150635-6dcec336b2bb h1:QIsP/NmClBICkqnJ4rSIhnrGiGR7Yv9ZORGGnmmLTPk=
golang.org/x/exp v0.0.0-20221204150635-6dcec336b2bb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,542 +0,0 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2020 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CGO
# include "MMKVPredef.h"
# include "MMKV.h"
# include "golang-bridge.h"
# include <stdint.h>
# include <string>
# include <cstring>
using namespace mmkv;
using namespace std;
# define MMKV_EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used))
MMKV_EXPORT void mmkvInitialize(GoStringWrap rootDir, int32_t logLevel) {
if (!rootDir.ptr) {
return;
}
MMKV::initializeMMKV(string(rootDir.ptr, rootDir.length), (MMKVLogLevel) logLevel);
}
MMKV_EXPORT void onExit() {
MMKV::onExit();
}
MMKV_EXPORT void *getMMKVWithID(GoStringWrap mmapID, int32_t mode, GoStringWrap cryptKey, GoStringWrap rootPath) {
MMKV *kv = nullptr;
if (!mmapID.ptr) {
return kv;
}
auto str = string(mmapID.ptr, mmapID.length);
bool done = false;
if (cryptKey.ptr) {
auto crypt = string(cryptKey.ptr, cryptKey.length);
if (crypt.length() > 0) {
if (rootPath.ptr) {
auto path = string(rootPath.ptr, rootPath.length);
kv = MMKV::mmkvWithID(str, (MMKVMode) mode, &crypt, &path);
} else {
kv = MMKV::mmkvWithID(str, (MMKVMode) mode, &crypt, nullptr);
}
done = true;
}
}
if (!done) {
if (rootPath.ptr) {
auto path = string(rootPath.ptr, rootPath.length);
kv = MMKV::mmkvWithID(str, (MMKVMode) mode, nullptr, &path);
} else {
kv = MMKV::mmkvWithID(str, (MMKVMode) mode, nullptr, nullptr);
}
}
return kv;
}
MMKV_EXPORT void *getDefaultMMKV(int32_t mode, GoStringWrap cryptKey) {
MMKV *kv = nullptr;
if (cryptKey.ptr) {
auto crypt = string(cryptKey.ptr, cryptKey.length);
if (crypt.length() > 0) {
kv = MMKV::defaultMMKV((MMKVMode) mode, &crypt);
}
}
if (!kv) {
kv = MMKV::defaultMMKV((MMKVMode) mode, nullptr);
}
return kv;
}
MMKV_EXPORT const char *mmapID(void *handle) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
return kv->mmapID().c_str();
}
return nullptr;
}
MMKV_EXPORT bool encodeBool(void *handle, GoStringWrap oKey, bool value) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->set((bool) value, key);
}
return false;
}
MMKV_EXPORT bool decodeBool(void *handle, GoStringWrap oKey, bool defaultValue) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->getBool(key, defaultValue);
}
return defaultValue;
}
MMKV_EXPORT bool encodeInt32(void *handle, GoStringWrap oKey, int32_t value) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->set((int32_t) value, key);
}
return false;
}
MMKV_EXPORT int32_t decodeInt32(void *handle, GoStringWrap oKey, int32_t defaultValue) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->getInt32(key, defaultValue);
}
return defaultValue;
}
MMKV_EXPORT bool encodeUInt32(void *handle, GoStringWrap oKey, uint32_t value) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->set(value, key);
}
return false;
}
MMKV_EXPORT uint32_t decodeUInt32(void *handle, GoStringWrap oKey, uint32_t defaultValue) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->getUInt32(key, defaultValue);
}
return defaultValue;
}
MMKV_EXPORT bool encodeInt64(void *handle, GoStringWrap oKey, int64_t value) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->set((int64_t) value, key);
}
return false;
}
MMKV_EXPORT int64_t decodeInt64(void *handle, GoStringWrap oKey, int64_t defaultValue) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->getInt64(key, defaultValue);
}
return defaultValue;
}
MMKV_EXPORT bool encodeUInt64(void *handle, GoStringWrap oKey, uint64_t value) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->set(value, key);
}
return false;
}
MMKV_EXPORT uint64_t decodeUInt64(void *handle, GoStringWrap oKey, uint64_t defaultValue) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->getUInt64(key, defaultValue);
}
return defaultValue;
}
MMKV_EXPORT bool encodeFloat(void *handle, GoStringWrap oKey, float value) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->set((float) value, key);
}
return false;
}
MMKV_EXPORT float decodeFloat(void *handle, GoStringWrap oKey, float defaultValue) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->getFloat(key, defaultValue);
}
return defaultValue;
}
MMKV_EXPORT bool encodeDouble(void *handle, GoStringWrap oKey, double value) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->set((double) value, key);
}
return false;
}
MMKV_EXPORT double decodeDouble(void *handle, GoStringWrap oKey, double defaultValue) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->getDouble(key, defaultValue);
}
return defaultValue;
}
MMKV_EXPORT bool encodeBytes(void *handle, GoStringWrap oKey, GoStringWrap oValue) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
if (oValue.ptr) {
auto value = MMBuffer((void *) oValue.ptr, oValue.length, MMBufferNoCopy);
return kv->set(value, key);
} else {
kv->removeValueForKey(key);
return true;
}
}
return false;
}
MMKV_EXPORT void *decodeBytes(void *handle, GoStringWrap oKey, uint64_t *lengthPtr) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
auto value = kv->getBytes(key);
if (value.length() > 0) {
if (value.isStoredOnStack()) {
auto result = malloc(value.length());
if (result) {
memcpy(result, value.getPtr(), value.length());
*lengthPtr = value.length();
}
return result;
} else {
void *result = value.getPtr();
*lengthPtr = value.length();
value.detach();
return result;
}
}
}
return nullptr;
}
# ifndef MMKV_DISABLE_CRYPT
MMKV_EXPORT bool reKey(void *handle, GoStringWrap oKey) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
if (oKey.ptr && oKey.length > 0) {
string key(oKey.ptr, oKey.length);
return kv->reKey(key);
} else {
return kv->reKey(string());
}
}
return false;
}
MMKV_EXPORT void *cryptKey(void *handle, uint32_t *lengthPtr) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && lengthPtr) {
auto cryptKey = kv->cryptKey();
if (cryptKey.length() > 0) {
auto ptr = malloc(cryptKey.length());
if (ptr) {
memcpy(ptr, cryptKey.data(), cryptKey.length());
*lengthPtr = cryptKey.length();
return ptr;
}
}
}
return nullptr;
}
MMKV_EXPORT void checkReSetCryptKey(void *handle, GoStringWrap oKey) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
if (oKey.ptr && oKey.length > 0) {
string key(oKey.ptr, oKey.length);
kv->checkReSetCryptKey(&key);
} else {
kv->checkReSetCryptKey(nullptr);
}
}
}
# endif // MMKV_DISABLE_CRYPT
MMKV_EXPORT GoStringWrap *allKeys(void *handle, uint64_t *lengthPtr) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
auto keys = kv->allKeys();
if (!keys.empty()) {
auto keyArray = (GoStringWrap *) calloc(keys.size(), sizeof(GoStringWrap));
if (!keyArray) {
return nullptr;
}
for (size_t index = 0; index < keys.size(); index++) {
auto &key = keys[index];
auto &stringWrap = keyArray[index];
stringWrap.length = static_cast<uint32_t>(key.length());
stringWrap.ptr = (char *) malloc(key.length());
if (stringWrap.ptr) {
memcpy((void *) stringWrap.ptr, key.data(), key.length());
}
}
*lengthPtr = keys.size();
return keyArray;
}
}
return nullptr;
}
MMKV_EXPORT bool containsKey(void *handle, GoStringWrap oKey) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
return kv->containsKey(key);
}
return false;
}
MMKV_EXPORT uint64_t count(void *handle) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
return kv->count();
}
return 0;
}
MMKV_EXPORT uint64_t totalSize(void *handle) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
return kv->totalSize();
}
return 0;
}
MMKV_EXPORT uint64_t actualSize(void *handle) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
return kv->actualSize();
}
return 0;
}
MMKV_EXPORT void removeValueForKey(void *handle, GoStringWrap oKey) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && oKey.ptr) {
auto key = string(oKey.ptr, oKey.length);
kv->removeValueForKey(key);
}
}
MMKV_EXPORT void removeValuesForKeys(void *handle, GoStringWrap *keyArray, uint64_t count) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv && keyArray && count > 0) {
vector<string> arrKeys;
arrKeys.reserve(count);
for (uint64_t index = 0; index < count; index++) {
auto &stringWrap = keyArray[index];
if (stringWrap.ptr && stringWrap.length > 0) {
arrKeys.emplace_back(stringWrap.ptr, stringWrap.length);
}
}
if (!arrKeys.empty()) {
kv->removeValuesForKeys(arrKeys);
}
}
}
MMKV_EXPORT void clearAll(void *handle) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
kv->clearAll();
}
}
MMKV_EXPORT void mmkvSync(void *handle, bool sync) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
kv->sync((SyncFlag) sync);
}
}
MMKV_EXPORT void clearMemoryCache(void *handle) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
kv->clearMemoryCache();
}
}
MMKV_EXPORT int32_t pageSize() {
return static_cast<int32_t>(DEFAULT_MMAP_SIZE);
}
MMKV_EXPORT const char *version() {
return MMKV_VERSION;
}
MMKV_EXPORT void trim(void *handle) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
kv->trim();
}
}
MMKV_EXPORT void mmkvClose(void *handle) {
MMKV *kv = static_cast<MMKV *>(handle);
if (kv) {
kv->close();
}
}
MMKV_EXPORT bool backupOneToDirectory(GoStringWrap_t mmapID, GoStringWrap_t dstDir, GoStringWrap_t srcDir) {
if (!mmapID.ptr || !dstDir.ptr) {
return false;
}
auto id = string(mmapID.ptr, mmapID.length);
auto dst = string(dstDir.ptr, dstDir.length);
if (srcDir.ptr) {
auto src = string(srcDir.ptr, srcDir.length);
return MMKV::backupOneToDirectory(id, dst, &src);
}
return MMKV::backupOneToDirectory(id, dst, nullptr);
}
MMKV_EXPORT bool restoreOneFromDirectory(GoStringWrap_t mmapID, GoStringWrap_t srcDir, GoStringWrap_t dstDir) {
if (!mmapID.ptr || !srcDir.ptr) {
return false;
}
auto id = string(mmapID.ptr, mmapID.length);
auto src = string(srcDir.ptr, srcDir.length);
if (dstDir.ptr) {
auto dst = string(dstDir.ptr, dstDir.length);
return MMKV::restoreOneFromDirectory(id, src, &dst);
}
return MMKV::restoreOneFromDirectory(id, src, nullptr);
}
MMKV_EXPORT uint64_t backupAllToDirectory(GoStringWrap_t dstDir, GoStringWrap_t srcDir) {
if (!dstDir.ptr) {
return 0;
}
auto dst = string(dstDir.ptr, dstDir.length);
if (srcDir.ptr) {
auto src = string(srcDir.ptr, srcDir.length);
return MMKV::backupAllToDirectory(dst, &src);
}
return MMKV::backupAllToDirectory(dst, nullptr);
}
MMKV_EXPORT uint64_t restoreAllFromDirectory(GoStringWrap_t srcDir, GoStringWrap_t dstDir) {
if (!srcDir.ptr) {
return 0;
}
auto src = string(srcDir.ptr, srcDir.length);
if (dstDir.ptr) {
auto dst = string(dstDir.ptr, dstDir.length);
return MMKV::restoreAllFromDirectory(src, &dst);
}
return MMKV::restoreAllFromDirectory(src, nullptr);
}
extern "C" void myLogHandler(int64_t level, GoStringWrap file, int64_t line, GoStringWrap function, GoStringWrap message);
void cLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) {
GoStringWrap oFile { file, static_cast<int64_t>(strlen(file)) };
GoStringWrap oFunction { function, static_cast<int64_t>(strlen(function)) };
GoStringWrap oMessage { message.data(), static_cast<int64_t>(message.length()) };
myLogHandler(level, oFile, line, oFunction, oMessage);
}
void setWantsLogRedirect(bool redirect) {
if (redirect) {
MMKV::registerLogHandler(&cLogHandler);
} else {
MMKV::unRegisterLogHandler();
}
}
extern "C" int64_t myErrorHandler(GoStringWrap mmapID, int64_t error);
static MMKVRecoverStrategic cErrorHandler(const std::string &mmapID, MMKVErrorType errorType) {
GoStringWrap oID { mmapID.data(), static_cast<int64_t>(mmapID.length()) };
return static_cast<MMKVRecoverStrategic>(myErrorHandler(oID, static_cast<int64_t>(errorType)));
}
void setWantsErrorHandle(bool errorHandle) {
if (errorHandle) {
MMKV::registerErrorHandler(&cErrorHandler);
} else {
MMKV::unRegisterErrorHandler();
}
}
extern "C" void myContentChangeHandler(GoStringWrap mmapID);
static void cContentChangeHandler(const std::string &mmapID) {
GoStringWrap oID { mmapID.data(), static_cast<int64_t>(mmapID.length()) };
myContentChangeHandler(oID);
}
void setWantsContentChangeHandle(bool errorHandle) {
if (errorHandle) {
MMKV::registerContentChangeHandler(&cContentChangeHandler);
} else {
MMKV::unRegisterContentChangeHandler();
}
}
#endif // CGO

View File

@ -1,106 +0,0 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2020 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.
*/
#ifdef __cplusplus
# include <cstdint>
extern "C" {
#else
# include <stdbool.h>
# include <stdint.h>
#endif
struct GoStringWrap {
const char *ptr;
int64_t length;
};
typedef struct GoStringWrap GoStringWrap_t;
struct GoSliceWrap {
void *array;
int64_t length;
int64_t capacity;
};
typedef struct GoSliceWrap GoSliceWrap_t;
void mmkvInitialize(GoStringWrap_t rootDir, int32_t logLevel);
void onExit();
void *getMMKVWithID(GoStringWrap_t mmapID, int32_t mode, GoStringWrap_t cryptKey, GoStringWrap_t rootPath);
void *getDefaultMMKV(int32_t mode, GoStringWrap_t cryptKey);
const char *mmapID(void *handle);
bool encodeBool(void *handle, GoStringWrap_t oKey, bool value);
bool decodeBool(void *handle, GoStringWrap_t oKey, bool defaultValue);
bool encodeInt32(void *handle, GoStringWrap_t oKey, int32_t value);
int32_t decodeInt32(void *handle, GoStringWrap_t oKey, int32_t defaultValue);
bool encodeUInt32(void *handle, GoStringWrap_t oKey, uint32_t value);
uint32_t decodeUInt32(void *handle, GoStringWrap_t oKey, uint32_t defaultValue);
bool encodeInt64(void *handle, GoStringWrap_t oKey, int64_t value);
int64_t decodeInt64(void *handle, GoStringWrap_t oKey, int64_t defaultValue);
bool encodeUInt64(void *handle, GoStringWrap_t oKey, uint64_t value);
uint64_t decodeUInt64(void *handle, GoStringWrap_t oKey, uint64_t defaultValue);
bool encodeFloat(void *handle, GoStringWrap_t oKey, float value);
float decodeFloat(void *handle, GoStringWrap_t oKey, float defaultValue);
bool encodeDouble(void *handle, GoStringWrap_t oKey, double value);
double decodeDouble(void *handle, GoStringWrap_t oKey, double defaultValue);
bool encodeBytes(void *handle, GoStringWrap_t oKey, GoStringWrap_t oValue);
void *decodeBytes(void *handle, GoStringWrap_t oKey, uint64_t *lengthPtr);
bool reKey(void *handle, GoStringWrap_t oKey);
void *cryptKey(void *handle, uint32_t *lengthPtr);
void checkReSetCryptKey(void *handle, GoStringWrap_t oKey);
GoStringWrap_t *allKeys(void *handle, uint64_t *lengthPtr);
bool containsKey(void *handle, GoStringWrap_t oKey);
uint64_t count(void *handle);
uint64_t totalSize(void *handle);
uint64_t actualSize(void *handle);
void removeValueForKey(void *handle, GoStringWrap_t oKey);
void removeValuesForKeys(void *handle, GoStringWrap_t *keyArray, uint64_t count);
void clearAll(void *handle);
void mmkvSync(void *handle, bool sync);
void clearMemoryCache(void *handle);
void trim(void *handle);
void mmkvClose(void *handle);
bool backupOneToDirectory(GoStringWrap_t mmapID, GoStringWrap_t dstDir, GoStringWrap_t srcDir);
bool restoreOneFromDirectory(GoStringWrap_t mmapID, GoStringWrap_t srcDir, GoStringWrap_t dstDir);
uint64_t backupAllToDirectory(GoStringWrap_t dstDir, GoStringWrap_t srcDir);
uint64_t restoreAllFromDirectory(GoStringWrap_t srcDir, GoStringWrap_t dstDir);
int32_t pageSize();
const char *version();
void setWantsLogRedirect(bool redirect);
void setWantsErrorHandle(bool errorHandle);
void setWantsContentChangeHandle(bool contentChange);
#ifdef __cplusplus
}
#endif

13
interface.go Normal file
View File

@ -0,0 +1,13 @@
package mmkv
type Manager interface {
// OpenVault opens a vault with the given id.
// If the vault does not exist, it will be created.
// If id is empty, DefaultVaultID will be used.
OpenVault(id string) (Vault, error)
}
type Vault interface {
Keys() []string
Get(key string) ([]byte, bool)
}

Binary file not shown.

Binary file not shown.

78
manager.go Normal file
View File

@ -0,0 +1,78 @@
package mmkv
import (
"fmt"
"os"
"path"
)
const (
DefaultVaultID = "mmkv.default"
)
type manager struct {
dir string
vaults map[string]Vault
}
// NewManager creates a new MMKV Manager.
func NewManager(dir string) (Manager, error) {
// check dir exists
info, err := os.Stat(dir)
if err != nil {
return nil, fmt.Errorf("failed to stat dir: %w", err)
}
if !info.IsDir() {
return nil, fmt.Errorf("not a directory")
}
return &manager{
dir: dir,
vaults: make(map[string]Vault),
}, nil
}
func (m *manager) OpenVault(id string) (Vault, error) {
if id == "" {
id = DefaultVaultID
}
if v, ok := m.vaults[id]; ok {
return v, nil
}
vault, err := m.openVault(id)
if err != nil {
return nil, fmt.Errorf("failed to open vault: %w", err)
}
m.vaults[id] = vault
return vault, nil
}
func (m *manager) openVault(id string) (Vault, error) {
metaFile, err := os.Open(path.Join(m.dir, id+".crc"))
if err != nil {
return nil, fmt.Errorf("failed to open metadata file: %w", err)
}
defer metaFile.Close()
vaultFile, err := os.Open(path.Join(m.dir, id))
if err != nil {
return nil, fmt.Errorf("failed to open vault file: %w", err)
}
defer vaultFile.Close()
meta, err := loadMetadata(metaFile)
if err != nil {
return nil, fmt.Errorf("failed to load metadata: %w", err)
}
v, err := loadVault(vaultFile, meta)
if err != nil {
return nil, fmt.Errorf("failed to load vault: %w", err)
}
return v, nil
}

17
manager_test.go Normal file
View File

@ -0,0 +1,17 @@
package mmkv
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewManager(t *testing.T) {
mgr, err := NewManager("./testdata")
assert.NoError(t, err)
assert.NotNil(t, mgr)
vault, err := mgr.OpenVault("")
assert.NoError(t, err)
assert.NotNil(t, vault)
}

56
metadata.go Normal file
View File

@ -0,0 +1,56 @@
package mmkv
import (
"encoding/binary"
"fmt"
"io"
)
type metadata struct {
// added in version 0
crc32 uint32
// added in version 1
version uint32
sequence uint32 // full write back count
// added in version 2
aesVector []byte // random iv for encryption, aes.BlockSize (16 bytes)
// added in version 3, try to reduce file corruption
actualSize uint32
lastActualSize uint32
lastCRC32 uint32
//_reversed []byte // 64 bytes
}
func loadMetadata(rd io.Reader) (*metadata, error) {
buf := make([]byte, 0x68)
_, err := io.ReadFull(rd, buf)
if err != nil {
return nil, fmt.Errorf("failed to read metadata: %w", err)
}
m := &metadata{}
m.crc32 = binary.LittleEndian.Uint32(buf[0:4])
m.version = binary.LittleEndian.Uint32(buf[4:8])
if m.version >= 1 {
m.sequence = binary.LittleEndian.Uint32(buf[8:12])
}
if m.version >= 2 {
m.aesVector = buf[12:28]
}
if m.version >= 3 {
m.actualSize = binary.LittleEndian.Uint32(buf[28:32])
m.lastActualSize = binary.LittleEndian.Uint32(buf[32:36])
m.lastCRC32 = binary.LittleEndian.Uint32(buf[36:40])
}
//m._reversed = buf[40:104]
return m, nil
}

29
metadata_test.go Normal file
View File

@ -0,0 +1,29 @@
package mmkv
import (
"bytes"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_loadMetadata(t *testing.T) {
file, err := os.Open("./testdata/mmkv.default.crc")
require.NoError(t, err)
meta, err := loadMetadata(file)
require.NoError(t, err)
assert.Equal(t, uint32(3), meta.version)
assert.Equal(t, uint32(1), meta.sequence)
assert.Equal(t, uint32(28), meta.actualSize)
assert.Equal(t, uint32(197326043), meta.crc32)
assert.Equal(t, uint32(4), meta.lastActualSize)
assert.Equal(t, uint32(1285129681), meta.lastCRC32)
assert.Equal(t, bytes.Repeat([]byte{0x00}, 16), meta.aesVector)
}

538
mmkv.go
View File

@ -1,538 +0,0 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2020 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.
*/
// MMKV is a cross-platform key-value storage framework developed by WeChat.
package mmkv
// #cgo CXXFLAGS: -D CGO -D FORCE_POSIX -I ${SRCDIR}/../../Core -std=c++17
// #cgo LDFLAGS: -L./lib -lmmkv -lcore -lz -lpthread
/*
#include "golang-bridge.h"
#include <stdlib.h>
typedef void* voidptr_t;
static GoStringWrap_t wrapGoString(_GoString_ str) {
GoStringWrap_t wrap = { _GoStringPtr(str), _GoStringLen(str) };
return wrap;
}
static GoStringWrap_t GoStringWrapNil() {
GoStringWrap_t result = { 0, 0 };
return result;
}
static GoStringWrap_t wrapGoByteSlice(const void *ptr, size_t len) {
GoStringWrap_t wrap = { ptr, len };
return wrap;
}
static void freeStringArray(GoStringWrap_t *a, size_t size) {
for (size_t i = 0; i < size; i++) {
free((void*) a[i].ptr);
}
}
*/
import "C"
import "unsafe"
const (
MMKVLogDebug = iota // not available for release/product build
MMKVLogInfo // default level
MMKVLogWarning
MMKVLogError
MMKVLogNone // special level used to disable all log messages
)
const (
MMKV_SINGLE_PROCESS = 1 << iota
MMKV_MULTI_PROCESS
)
// a wrapper of native C memory, efficient for simple usage
// must call MMBuffer.Destroy() after no longer usage
type MMBuffer struct {
ptr uintptr
length int
}
// the address of underlying memory
func (buffer MMBuffer) Pointer() uintptr {
return buffer.ptr
}
// the size of underlying memory
func (buffer MMBuffer) Length() int {
return buffer.length
}
// get byte slice view of underlying memory
// the slice is valid as long as MMBuffer.Destroy() not called
func (buffer MMBuffer) ByteSliceView() []byte {
bytes := (*[1 << 30]byte)(unsafe.Pointer(buffer.ptr))[0:buffer.length:buffer.length]
return bytes
}
// get string view of underlying memory
// the string is valid as long as MMBuffer.Destroy() not called
func (buffer MMBuffer) StringView() string {
return *((*string)(unsafe.Pointer(&buffer)))
}
// must call Destroy() after no longer usage
func (buffer MMBuffer) Destroy() {
C.free(unsafe.Pointer(buffer.ptr))
}
type MMKV interface {
SetBool(value bool, key string) bool
GetBool(key string) bool
GetBoolWithDefault(key string, defaultValue bool) bool
SetInt32(value int32, key string) bool
GetInt32(key string) int32
GetInt32WithDefault(key string, defaultValue int32) int32
SetInt64(value int64, key string) bool
GetInt64(key string) int64
GetInt64WithDefault(key string, defaultValue int64) int64
SetUInt32(value uint32, key string) bool
GetUInt32(key string) uint32
GetUInt32WithDefault(key string, defaultValue uint32) uint32
SetUInt64(value uint64, key string) bool
GetUInt64(key string) uint64
GetUInt64WithDefault(key string, defaultValue uint64) uint64
SetFloat32(value float32, key string) bool
GetFloat32(key string) float32
GetFloat32WithDefault(key string, defaultValue float32) float32
SetFloat64(value float64, key string) bool
GetFloat64(key string) float64
GetFloat64WithDefault(key string, defaultValue float64) float64
// string value should be utf-8 encoded
SetString(value string, key string) bool
GetString(key string) string
// get C memory directly (without memcpy), much more efferent for large value
GetStringBuffer(key string) MMBuffer
SetBytes(value []byte, key string) bool
GetBytes(key string) []byte
// get C memory directly (without memcpy), much more efferent for large value
GetBytesBuffer(key string) MMBuffer
RemoveKey(key string)
RemoveKeys(keys []string)
// clear all key-values
ClearAll()
// return count of keys
Count() uint64
AllKeys() []string
Contains(key string) bool
// total size of the file
TotalSize() uint64
// actual used size of the file
ActualSize() uint64
// the mmapID of the instance
MMAP_ID() string
/* Synchronize memory to file. You don't need to call this, really, I mean it. Unless you worry about running out of battery.
* Pass true to perform synchronous write.
* Pass false to perform asynchronous write, return immediately.
*/
Sync(sync bool)
// Clear all caches (on memory warning).
ClearMemoryCache()
/* Trim the file size to minimal.
* MMKV's size won't reduce after deleting key-values.
* Call this method after lots of deleting if you care about disk usage.
* Note that clearAll() has the similar effect.
*/
Trim()
// Close the instance when it's no longer needed in the near future.
// Any subsequent call to the instance is undefined behavior.
Close()
/* Change encryption key for the MMKV instance.
* The cryptKey is 16 bytes limited.
* You can transfer a plain-text MMKV into encrypted by setting an non-null, non-empty cryptKey.
* Or vice versa by passing cryptKey with null. See also checkReSetCryptKey().
*/
ReKey(newKey string) bool
// Just reset the cryptKey (will not encrypt or decrypt anything).
// Usually you should call this method after other process reKey() the multi-process mmkv.
CheckReSetCryptKey(key string)
// See also reKey().
CryptKey() string
}
type ctorMMKV uintptr
// return the version of MMKV
func Version() string {
version := C.version()
goStr := C.GoString(version)
return goStr
}
/* MMKV must be initialized before any usage.
* Generally speaking you should do this inside main():
func main() {
mmkv.InitializeMMKV("/path/to/my/working/dir")
// other logic
}
*/
func InitializeMMKV(rootDir string) {
C.mmkvInitialize(C.wrapGoString(rootDir), MMKVLogInfo)
}
// Same as the function InitializeMMKV() above, except that you can customize MMKV's log level by passing logLevel.
// You can even turnoff logging by passing MMKVLogNone, which we don't recommend doing.
func InitializeMMKVWithLogLevel(rootDir string, logLevel int) {
C.mmkvInitialize(C.wrapGoString(rootDir), C.int32_t(logLevel))
}
// Call before App exists, it's just fine not calling it on most case (except when the device shutdown suddenly).
func OnExit() {
C.onExit()
}
// return the page size of memory
func PageSize() int32 {
return int32(C.pageSize())
}
// a generic purpose instance in single-process mode.
func DefaultMMKV() MMKV {
mmkv := ctorMMKV(C.getDefaultMMKV(MMKV_SINGLE_PROCESS, C.GoStringWrapNil()))
return MMKV(mmkv)
}
// a generic purpose instance in single-process or multi-process mode.
func DefaultMMKVWithMode(mode int) MMKV {
mmkv := ctorMMKV(C.getDefaultMMKV(C.int(mode), C.GoStringWrapNil()))
return MMKV(mmkv)
}
// an encrypted generic purpose instance in single-process or multi-process mode.
func DefaultMMKVWithModeAndCryptKey(mode int, cryptKey string) MMKV {
mmkv := ctorMMKV(C.getDefaultMMKV(MMKV_SINGLE_PROCESS, C.wrapGoString(cryptKey)))
return MMKV(mmkv)
}
// an instance with specific location ${MMKV Root}/mmapID, in single-process mode.
func MMKVWithID(mmapID string) MMKV {
cStrNull := C.GoStringWrapNil()
mmkv := ctorMMKV(C.getMMKVWithID(C.wrapGoString(mmapID), MMKV_SINGLE_PROCESS, cStrNull, cStrNull))
return MMKV(mmkv)
}
// an instance with specific location ${MMKV Root}/mmapID, in single-process or multi-process mode.
func MMKVWithIDAndMode(mmapID string, mode int) MMKV {
cStrNull := C.GoStringWrapNil()
mmkv := ctorMMKV(C.getMMKVWithID(C.wrapGoString(mmapID), C.int(mode), cStrNull, cStrNull))
return MMKV(mmkv)
}
// an encrypted instance with specific location ${MMKV Root}/mmapID, in single-process or multi-process mode.
func MMKVWithIDAndModeAndCryptKey(mmapID string, mode int, cryptKey string) MMKV {
cStrNull := C.GoStringWrapNil()
mmkv := ctorMMKV(C.getMMKVWithID(C.wrapGoString(mmapID), C.int(mode), C.wrapGoString(cryptKey), cStrNull))
return MMKV(mmkv)
}
// backup one MMKV instance (from the root dir of MMKV) to dstDir
func BackupOneToDirectory(mmapID string, dstDir string) bool {
cStrNull := C.GoStringWrapNil()
ret := C.backupOneToDirectory(C.wrapGoString(mmapID), C.wrapGoString(dstDir), cStrNull)
return bool(ret)
}
// restore one MMKV instance from srcDir (to the root dir of MMKV)
func RestoreOneFromDirectory(mmapID string, srcDir string) bool {
cStrNull := C.GoStringWrapNil()
ret := C.restoreOneFromDirectory(C.wrapGoString(mmapID), C.wrapGoString(srcDir), cStrNull)
return bool(ret)
}
// backup all MMKV instance (from the root dir of MMKV) to dstDir
// return count of MMKV successfully backup-ed
func BackupAllToDirectory(dstDir string) uint64 {
cStrNull := C.GoStringWrapNil()
ret := C.backupAllToDirectory(C.wrapGoString(dstDir), cStrNull)
return uint64(ret)
}
// restore all MMKV instance from srcDir (to the root dir of MMKV)
// return count of MMKV successfully restored
func RestoreAllFromDirectory(srcDir string) uint64 {
cStrNull := C.GoStringWrapNil()
ret := C.restoreAllFromDirectory(C.wrapGoString(srcDir), cStrNull)
return uint64(ret)
}
func (kv ctorMMKV) SetBool(value bool, key string) bool {
ret := C.encodeBool(unsafe.Pointer(kv), C.wrapGoString(key), C.bool(value))
return bool(ret)
}
func (kv ctorMMKV) GetBool(key string) bool {
return kv.GetBoolWithDefault(key, false)
}
func (kv ctorMMKV) GetBoolWithDefault(key string, defaultValue bool) bool {
value := C.decodeBool(unsafe.Pointer(kv), C.wrapGoString(key), C.bool(defaultValue))
return bool(value)
}
func (kv ctorMMKV) SetInt32(value int32, key string) bool {
ret := C.encodeInt32(unsafe.Pointer(kv), C.wrapGoString(key), C.int32_t(value))
return bool(ret)
}
func (kv ctorMMKV) GetInt32(key string) int32 {
return kv.GetInt32WithDefault(key, 0)
}
func (kv ctorMMKV) GetInt32WithDefault(key string, defaultValue int32) int32 {
value := C.decodeInt32(unsafe.Pointer(kv), C.wrapGoString(key), C.int32_t(defaultValue))
return int32(value)
}
func (kv ctorMMKV) SetUInt32(value uint32, key string) bool {
ret := C.encodeUInt32(unsafe.Pointer(kv), C.wrapGoString(key), C.uint32_t(value))
return bool(ret)
}
func (kv ctorMMKV) GetUInt32(key string) uint32 {
return kv.GetUInt32WithDefault(key, 0)
}
func (kv ctorMMKV) GetUInt32WithDefault(key string, defaultValue uint32) uint32 {
value := C.decodeUInt32(unsafe.Pointer(kv), C.wrapGoString(key), C.uint32_t(defaultValue))
return uint32(value)
}
func (kv ctorMMKV) SetInt64(value int64, key string) bool {
ret := C.encodeInt64(unsafe.Pointer(kv), C.wrapGoString(key), C.int64_t(value))
return bool(ret)
}
func (kv ctorMMKV) GetInt64(key string) int64 {
return kv.GetInt64WithDefault(key, 0)
}
func (kv ctorMMKV) GetInt64WithDefault(key string, defaultValue int64) int64 {
value := C.decodeInt64(unsafe.Pointer(kv), C.wrapGoString(key), C.int64_t(defaultValue))
return int64(value)
}
func (kv ctorMMKV) SetUInt64(value uint64, key string) bool {
ret := C.encodeUInt64(unsafe.Pointer(kv), C.wrapGoString(key), C.uint64_t(value))
return bool(ret)
}
func (kv ctorMMKV) GetUInt64(key string) uint64 {
return kv.GetUInt64WithDefault(key, 0)
}
func (kv ctorMMKV) GetUInt64WithDefault(key string, defaultValue uint64) uint64 {
value := C.decodeUInt64(unsafe.Pointer(kv), C.wrapGoString(key), C.uint64_t(defaultValue))
return uint64(value)
}
func (kv ctorMMKV) SetFloat32(value float32, key string) bool {
ret := C.encodeFloat(unsafe.Pointer(kv), C.wrapGoString(key), C.float(value))
return bool(ret)
}
func (kv ctorMMKV) GetFloat32(key string) float32 {
return kv.GetFloat32WithDefault(key, 0)
}
func (kv ctorMMKV) GetFloat32WithDefault(key string, defaultValue float32) float32 {
value := C.decodeFloat(unsafe.Pointer(kv), C.wrapGoString(key), C.float(defaultValue))
return float32(value)
}
func (kv ctorMMKV) SetFloat64(value float64, key string) bool {
ret := C.encodeDouble(unsafe.Pointer(kv), C.wrapGoString(key), C.double(value))
return bool(ret)
}
func (kv ctorMMKV) GetFloat64(key string) float64 {
return kv.GetFloat64WithDefault(key, 0)
}
func (kv ctorMMKV) GetFloat64WithDefault(key string, defaultValue float64) float64 {
value := C.decodeDouble(unsafe.Pointer(kv), C.wrapGoString(key), C.double(defaultValue))
return float64(value)
}
func (kv ctorMMKV) SetString(value string, key string) bool {
cValue := C.wrapGoString(value)
ret := C.encodeBytes(unsafe.Pointer(kv), C.wrapGoString(key), cValue)
return bool(ret)
}
func (kv ctorMMKV) GetString(key string) string {
var length uint64
cValue := C.decodeBytes(unsafe.Pointer(kv), C.wrapGoString(key), (*C.uint64_t)(&length))
value := C.GoStringN((*C.char)(cValue), C.int(length))
C.free(unsafe.Pointer(cValue))
return value
}
func (kv ctorMMKV) GetStringBuffer(key string) MMBuffer {
return kv.GetBytesBuffer(key)
}
func (kv ctorMMKV) SetBytes(value []byte, key string) bool {
cValue := C.wrapGoByteSlice(unsafe.Pointer(&value[0]), C.size_t(len(value)))
ret := C.encodeBytes(unsafe.Pointer(kv), C.wrapGoString(key), cValue)
return bool(ret)
}
func (kv ctorMMKV) GetBytes(key string) []byte {
var length uint64
cValue := C.decodeBytes(unsafe.Pointer(kv), C.wrapGoString(key), (*C.uint64_t)(&length))
value := C.GoBytes(unsafe.Pointer(cValue), C.int(length))
C.free(unsafe.Pointer(cValue))
return value
}
func (kv ctorMMKV) GetBytesBuffer(key string) MMBuffer {
var length uint64
cValue := C.decodeBytes(unsafe.Pointer(kv), C.wrapGoString(key), (*C.uint64_t)(&length))
value := MMBuffer{uintptr(cValue), int(length)}
return value
}
func (kv ctorMMKV) RemoveKey(key string) {
C.removeValueForKey(unsafe.Pointer(kv), C.wrapGoString(key))
}
func (kv ctorMMKV) RemoveKeys(keys []string) {
keyArray := (*C.struct_GoStringWrap)(unsafe.Pointer(&keys[0]))
C.removeValuesForKeys(unsafe.Pointer(kv), keyArray, C.uint64_t(len(keys)))
}
func (kv ctorMMKV) Count() uint64 {
return uint64(C.count(unsafe.Pointer(kv)))
}
func (kv ctorMMKV) AllKeys() []string {
count := uint64(0)
keyArray := C.allKeys(unsafe.Pointer(kv), (*C.uint64_t)(&count))
if keyArray == nil || count == 0 {
return []string{}
}
// turn C array into go slice with offset(0), length(count) & capacity(count)
keys := (*[1 << 30]C.struct_GoStringWrap)(unsafe.Pointer(keyArray))[0:count:count]
// Actually the keys IS a go string slice, but we need to COPY the elements for the caller to use.
// Too bad go doesn't has destructors, hence we can't simply TRANSFER ownership of C memory.
result := make([]string, count)
for index := uint64(0); index < count; index++ {
key := keys[index]
result[index] = C.GoStringN(key.ptr, C.int(key.length))
}
C.freeStringArray(keyArray, C.size_t(count))
C.free(unsafe.Pointer(keyArray))
return result
}
func (kv ctorMMKV) Contains(key string) bool {
ret := C.containsKey(unsafe.Pointer(kv), C.wrapGoString(key))
return bool(ret)
}
func (kv ctorMMKV) ClearAll() {
C.clearAll(unsafe.Pointer(kv))
}
func (kv ctorMMKV) TotalSize() uint64 {
return uint64(C.totalSize(unsafe.Pointer(kv)))
}
func (kv ctorMMKV) ActualSize() uint64 {
return uint64(C.actualSize(unsafe.Pointer(kv)))
}
func (kv ctorMMKV) MMAP_ID() string {
cStr := C.mmapID(unsafe.Pointer(kv))
return C.GoString(cStr)
}
func (kv ctorMMKV) Sync(sync bool) {
C.mmkvSync(unsafe.Pointer(kv), C.bool(sync))
}
func (kv ctorMMKV) ClearMemoryCache() {
C.clearMemoryCache(unsafe.Pointer(kv))
}
func (kv ctorMMKV) Trim() {
C.trim(unsafe.Pointer(kv))
}
func (kv ctorMMKV) Close() {
C.mmkvClose(unsafe.Pointer(kv))
}
func (kv ctorMMKV) ReKey(newKey string) bool {
ret := C.reKey(unsafe.Pointer(kv), C.wrapGoString(newKey))
return bool(ret)
}
func (kv ctorMMKV) CheckReSetCryptKey(key string) {
C.checkReSetCryptKey(unsafe.Pointer(kv), C.wrapGoString(key))
}
func (kv ctorMMKV) CryptKey() string {
var cLen C.uint32_t
cStr := C.cryptKey(unsafe.Pointer(kv), &cLen)
if cStr == nil || cLen == 0 {
return ""
}
result := C.GoStringN((*C.char)(cStr), C.int(cLen))
C.free(unsafe.Pointer(cStr))
return result
}

View File

@ -1,33 +0,0 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2020 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.
*/
package mmkv
import (
"testing"
// "regexp"
)
func TestVersionEmpty(t *testing.T) {
msg := Version()
if msg == "" {
t.Fatalf("Version() == \"\"")
}
}

BIN
testdata/mmkv.default vendored Executable file

Binary file not shown.

BIN
testdata/mmkv.default.crc vendored Executable file

Binary file not shown.

72
vault.go Normal file
View File

@ -0,0 +1,72 @@
package mmkv
import (
"encoding/binary"
"fmt"
"hash/crc32"
"io"
"github.com/golang/protobuf/proto"
"golang.org/x/exp/maps"
)
type vault map[string][]byte
func (v vault) Keys() []string {
return maps.Keys(v)
}
func (v vault) Get(key string) ([]byte, bool) {
val, ok := v[key]
return val, ok
}
// metadata is optional. but if it exists, validate with it.
func loadVault(src io.Reader, m *metadata) (Vault, error) {
fileSizeBuf := make([]byte, 4)
_, err := io.ReadFull(src, fileSizeBuf)
if err != nil {
return nil, fmt.Errorf("failed to read file size: %w", err)
}
size := binary.LittleEndian.Uint32(fileSizeBuf)
if m != nil && size != m.actualSize {
return nil, fmt.Errorf("metadata and vault payload size mismatch")
}
buf := make([]byte, size)
_, err = io.ReadFull(src, buf)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
if m != nil && m.crc32 != crc32.ChecksumIEEE(buf) {
return nil, fmt.Errorf("metadata and vault payload crc32 mismatch")
}
v := make(vault)
// mmkv is not really protobuf compatible,
// type of key & value (the first 4 bytes) is incorrect.
// so skip the first 4 bytes & manually parse the rest.
rd := proto.NewBuffer(buf[4:])
for {
if len(rd.Unread()) == 0 {
break
}
key, err := rd.DecodeStringBytes()
if err != nil {
return nil, fmt.Errorf("failed to decode key: %w", err)
}
val, err := rd.DecodeRawBytes(false)
if err != nil {
return nil, fmt.Errorf("failed to decode value: %w", err)
}
v[key] = val
}
return v, nil
}

26
vault_test.go Normal file
View File

@ -0,0 +1,26 @@
package mmkv
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_loadVault(t *testing.T) {
file, err := os.Open("./testdata/mmkv.default")
require.NoError(t, err)
v, err := loadVault(file, nil)
require.NoError(t, err)
assert.Equal(t, 2, len(v.Keys()))
val, ok := v.Get("world")
assert.Equal(t, "hello", string(val))
assert.True(t, ok)
val, ok = v.Get("foo")
assert.False(t, ok)
}