go-mmkv/mmkv.go
2022-12-04 22:42:29 +08:00

539 lines
16 KiB
Go

/*
* 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
}