diff --git a/callback.go b/callback.go deleted file mode 100644 index a2dbf51..0000000 --- a/callback.go +++ /dev/null @@ -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 -*/ -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) - } -} diff --git a/go.mod b/go.mod index 3f7caeb..efdd6af 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,16 @@ 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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9e8f13c --- /dev/null +++ b/go.sum @@ -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= diff --git a/golang-bridge.cpp b/golang-bridge.cpp deleted file mode 100644 index f687025..0000000 --- a/golang-bridge.cpp +++ /dev/null @@ -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 -# include -# include - -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(handle); - if (kv) { - return kv->mmapID().c_str(); - } - return nullptr; -} - -MMKV_EXPORT bool encodeBool(void *handle, GoStringWrap oKey, bool value) { - MMKV *kv = static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(handle); - if (kv) { - return kv->count(); - } - return 0; -} - -MMKV_EXPORT uint64_t totalSize(void *handle) { - MMKV *kv = static_cast(handle); - if (kv) { - return kv->totalSize(); - } - return 0; -} - -MMKV_EXPORT uint64_t actualSize(void *handle) { - MMKV *kv = static_cast(handle); - if (kv) { - return kv->actualSize(); - } - return 0; -} - -MMKV_EXPORT void removeValueForKey(void *handle, GoStringWrap oKey) { - MMKV *kv = static_cast(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(handle); - if (kv && keyArray && count > 0) { - vector 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(handle); - if (kv) { - kv->clearAll(); - } -} - -MMKV_EXPORT void mmkvSync(void *handle, bool sync) { - MMKV *kv = static_cast(handle); - if (kv) { - kv->sync((SyncFlag) sync); - } -} - -MMKV_EXPORT void clearMemoryCache(void *handle) { - MMKV *kv = static_cast(handle); - if (kv) { - kv->clearMemoryCache(); - } -} - -MMKV_EXPORT int32_t pageSize() { - return static_cast(DEFAULT_MMAP_SIZE); -} - -MMKV_EXPORT const char *version() { - return MMKV_VERSION; -} - -MMKV_EXPORT void trim(void *handle) { - MMKV *kv = static_cast(handle); - if (kv) { - kv->trim(); - } -} - -MMKV_EXPORT void mmkvClose(void *handle) { - MMKV *kv = static_cast(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(strlen(file)) }; - GoStringWrap oFunction { function, static_cast(strlen(function)) }; - GoStringWrap oMessage { message.data(), static_cast(message.length()) }; - - myLogHandler(level, oFile, line, oFunction, oMessage); -} - -void setWantsLogRedirect(bool redirect) { - if (redirect) { - MMKV::registerLogHandler(&cLogHandler); - } else { - MMKV::unRegisterLogHandler(); - } -} - -extern "C" int64_t myErrorHandler(GoStringWrap mmapID, int64_t error); - -static MMKVRecoverStrategic cErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; - - return static_cast(myErrorHandler(oID, static_cast(errorType))); -} - -void setWantsErrorHandle(bool errorHandle) { - if (errorHandle) { - MMKV::registerErrorHandler(&cErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -extern "C" void myContentChangeHandler(GoStringWrap mmapID); - -static void cContentChangeHandler(const std::string &mmapID) { - GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; - - myContentChangeHandler(oID); -} - -void setWantsContentChangeHandle(bool errorHandle) { - if (errorHandle) { - MMKV::registerContentChangeHandler(&cContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); - } -} - -#endif // CGO diff --git a/golang-bridge.h b/golang-bridge.h deleted file mode 100644 index 1599bcc..0000000 --- a/golang-bridge.h +++ /dev/null @@ -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 -extern "C" { -#else -# include -# include -#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 diff --git a/interface.go b/interface.go new file mode 100644 index 0000000..84b3c84 --- /dev/null +++ b/interface.go @@ -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) +} diff --git a/lib/libcore.a b/lib/libcore.a deleted file mode 100644 index 05f4411..0000000 Binary files a/lib/libcore.a and /dev/null differ diff --git a/lib/libmmkv.a b/lib/libmmkv.a deleted file mode 100644 index 3b5ac90..0000000 Binary files a/lib/libmmkv.a and /dev/null differ diff --git a/manager.go b/manager.go new file mode 100644 index 0000000..31ad59a --- /dev/null +++ b/manager.go @@ -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 +} diff --git a/manager_test.go b/manager_test.go new file mode 100644 index 0000000..9bdd64d --- /dev/null +++ b/manager_test.go @@ -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) +} diff --git a/metadata.go b/metadata.go new file mode 100644 index 0000000..4670278 --- /dev/null +++ b/metadata.go @@ -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 +} diff --git a/metadata_test.go b/metadata_test.go new file mode 100644 index 0000000..a9af8f0 --- /dev/null +++ b/metadata_test.go @@ -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) +} diff --git a/mmkv.go b/mmkv.go deleted file mode 100644 index d9b5089..0000000 --- a/mmkv.go +++ /dev/null @@ -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 - -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 -} diff --git a/mmkv_test.go b/mmkv_test.go deleted file mode 100644 index b84e097..0000000 --- a/mmkv_test.go +++ /dev/null @@ -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() == \"\"") - } -} diff --git a/testdata/mmkv.default b/testdata/mmkv.default new file mode 100755 index 0000000..83a8ca0 Binary files /dev/null and b/testdata/mmkv.default differ diff --git a/testdata/mmkv.default.crc b/testdata/mmkv.default.crc new file mode 100755 index 0000000..a80437a Binary files /dev/null and b/testdata/mmkv.default.crc differ diff --git a/vault.go b/vault.go new file mode 100644 index 0000000..c7da235 --- /dev/null +++ b/vault.go @@ -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 +} diff --git a/vault_test.go b/vault_test.go new file mode 100644 index 0000000..5732410 --- /dev/null +++ b/vault_test.go @@ -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) +}