mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-01-03 22:13:12 +00:00
520 lines
12 KiB
Go
520 lines
12 KiB
Go
package main
|
|
|
|
/*
|
|
#cgo LDFLAGS: -L../../build/ -lcodex
|
|
#cgo LDFLAGS: -L../../ -Wl,-rpath,../../
|
|
|
|
#include "../../library/libcodex.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
|
|
void libcodexNimMain(void);
|
|
static void codex_host_init_once(void){
|
|
static int done;
|
|
if (!__atomic_exchange_n(&done, 1, __ATOMIC_SEQ_CST)) libcodexNimMain();
|
|
}
|
|
|
|
extern void globalEventCallback(int ret, char* msg, size_t len, void* userData);
|
|
|
|
typedef struct {
|
|
int ret;
|
|
char* msg;
|
|
size_t len;
|
|
uintptr_t h;
|
|
} Resp;
|
|
|
|
static void* allocResp() {
|
|
return calloc(1, sizeof(Resp));
|
|
}
|
|
|
|
static void* allocRespWithHandle(uintptr_t h) {
|
|
Resp* r = (Resp*)calloc(1, sizeof(Resp));
|
|
r->h = h;
|
|
return r;
|
|
}
|
|
|
|
static void freeResp(void* resp) {
|
|
if (resp != NULL) {
|
|
free(resp);
|
|
}
|
|
}
|
|
|
|
static char* getMyCharPtr(void* resp) {
|
|
if (resp == NULL) {
|
|
return NULL;
|
|
}
|
|
Resp* m = (Resp*) resp;
|
|
return m->msg;
|
|
}
|
|
|
|
static size_t getMyCharLen(void* resp) {
|
|
if (resp == NULL) {
|
|
return 0;
|
|
}
|
|
Resp* m = (Resp*) resp;
|
|
return m->len;
|
|
}
|
|
|
|
static int getRet(void* resp) {
|
|
if (resp == NULL) {
|
|
return 0;
|
|
}
|
|
Resp* m = (Resp*) resp;
|
|
return m->ret;
|
|
}
|
|
|
|
// resp must be set != NULL in case interest on retrieving data from the callback
|
|
void callback(int ret, char* msg, size_t len, void* resp);
|
|
|
|
#define CODEX_CALL(call) \
|
|
do { \
|
|
int ret = call; \
|
|
if (ret != RET_OK && ret != RET_ACK) { \
|
|
printf("Failed the call to: %s. Returned code: %d\n", #call, ret); \
|
|
exit(1); \
|
|
} \
|
|
} while (0)
|
|
|
|
static void* cGoCodexNew(const char* configJson, void* resp) {
|
|
void* ret = codex_new(configJson, (CodexCallback) callback, resp);
|
|
return ret;
|
|
}
|
|
|
|
static void cGoCodexVersion(void* codexCtx, void* resp) {
|
|
CODEX_CALL(codex_version(codexCtx, (CodexCallback) callback, resp));
|
|
}
|
|
|
|
static void cGoCodexRevision(void* codexCtx, void* resp) {
|
|
CODEX_CALL(codex_revision(codexCtx, (CodexCallback) callback, resp));
|
|
}
|
|
|
|
static void cGoCodexRepo(void* codexCtx, void* resp) {
|
|
CODEX_CALL(codex_repo(codexCtx, (CodexCallback) callback, resp));
|
|
}
|
|
|
|
static void cGoCodexStart(void* codexCtx, void* resp) {
|
|
CODEX_CALL(codex_start(codexCtx, (CodexCallback) callback, resp));
|
|
}
|
|
|
|
static void cGoCodexStop(void* codexCtx, void* resp) {
|
|
CODEX_CALL(codex_stop(codexCtx, (CodexCallback) callback, resp));
|
|
}
|
|
|
|
static void cGoCodexDestroy(void* codexCtx, void* resp) {
|
|
CODEX_CALL(codex_destroy(codexCtx, (CodexCallback) callback, resp));
|
|
}
|
|
|
|
static void cGoCodexSetEventCallback(void* codexCtx) {
|
|
// The 'globalEventCallback' Go function is shared amongst all possible Codex instances.
|
|
|
|
// Given that the 'globalEventCallback' is shared, we pass again the
|
|
// codexCtx instance but in this case is needed to pick up the correct method
|
|
// that will handle the event.
|
|
|
|
// In other words, for every call the libcodex makes to globalEventCallback,
|
|
// the 'userData' parameter will bring the context of the node that registered
|
|
// that globalEventCallback.
|
|
|
|
// This technique is needed because cgo only allows to export Go functions and not methods.
|
|
|
|
codex_set_event_callback(codexCtx, (CodexCallback) globalEventCallback, codexCtx);
|
|
}
|
|
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"runtime/cgo"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
type LogLevel string
|
|
|
|
const (
|
|
Trace LogLevel = "TRACE"
|
|
Debug LogLevel = "DEBUG"
|
|
Info LogLevel = "INFO"
|
|
Notice LogLevel = "NOTICE"
|
|
Warn LogLevel = "WARN"
|
|
Error LogLevel = "ERROR"
|
|
Fatal LogLevel = "FATAL"
|
|
)
|
|
|
|
type LogFormat string
|
|
|
|
const (
|
|
LogFormatAuto LogFormat = "auto"
|
|
LogFormatColors LogFormat = "colors"
|
|
LogFormatNoColors LogFormat = "nocolors"
|
|
LogFormatJSON LogFormat = "json"
|
|
)
|
|
|
|
type RepoKind string
|
|
|
|
const (
|
|
FS RepoKind = "fs"
|
|
SQLite RepoKind = "sqlite"
|
|
LevelDb RepoKind = "leveldb"
|
|
)
|
|
|
|
type CodexConfig struct {
|
|
LogLevel LogLevel `json:"log-level,omitempty"`
|
|
LogFormat LogFormat `json:"log-format,omitempty"`
|
|
MetricsEnabled bool `json:"metrics,omitempty"`
|
|
MetricsAddress string `json:"metrics-address,omitempty"`
|
|
DataDir string `json:"data-dir,omitempty"`
|
|
ListenAddrs []string `json:"listen-addrs,omitempty"`
|
|
Nat string `json:"nat,omitempty"`
|
|
DiscoveryPort int `json:"disc-port,omitempty"`
|
|
NetPrivKeyFile string `json:"net-privkey,omitempty"`
|
|
BootstrapNodes []byte `json:"bootstrap-node,omitempty"`
|
|
MaxPeers int `json:"max-peers,omitempty"`
|
|
NumThreads int `json:"num-threads,omitempty"`
|
|
AgentString string `json:"agent-string,omitempty"`
|
|
RepoKind RepoKind `json:"repo-kind,omitempty"`
|
|
StorageQuota int `json:"storage-quota,omitempty"`
|
|
BlockTtl int `json:"block-ttl,omitempty"`
|
|
BlockMaintenanceInterval int `json:"block-mi,omitempty"`
|
|
BlockMaintenanceNumberOfBlocks int `json:"block-mn,omitempty"`
|
|
CacheSize int `json:"cache-size,omitempty"`
|
|
LogFile string `json:"log-file,omitempty"`
|
|
}
|
|
|
|
type CodexNode struct {
|
|
ctx unsafe.Pointer
|
|
}
|
|
|
|
type bridgeCtx struct {
|
|
wg *sync.WaitGroup
|
|
h cgo.Handle
|
|
resp unsafe.Pointer
|
|
result string
|
|
err error
|
|
}
|
|
|
|
func newBridgeCtx() *bridgeCtx {
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
bridge := &bridgeCtx{wg: &wg}
|
|
bridge.h = cgo.NewHandle(bridge)
|
|
bridge.resp = C.allocRespWithHandle(C.uintptr_t(uintptr(bridge.h)))
|
|
|
|
return bridge
|
|
}
|
|
|
|
func (b *bridgeCtx) free() {
|
|
C.freeResp(b.resp)
|
|
b.resp = nil
|
|
}
|
|
|
|
func (b *bridgeCtx) isACK() bool {
|
|
return C.getRet(b.resp) == C.RET_ACK
|
|
}
|
|
|
|
func (b *bridgeCtx) isOK() bool {
|
|
return C.getRet(b.resp) == C.RET_OK
|
|
}
|
|
|
|
func (b *bridgeCtx) isError() bool {
|
|
return C.getRet(b.resp) == C.RET_ERR
|
|
}
|
|
|
|
// TODO: Check the error here after the wait
|
|
func (b *bridgeCtx) wait() error {
|
|
b.wg.Wait()
|
|
return b.err
|
|
}
|
|
|
|
func (b *bridgeCtx) getMsg() string {
|
|
return C.GoStringN(C.getMyCharPtr(b.resp), C.int(C.getMyCharLen(b.resp)))
|
|
}
|
|
|
|
func (b *bridgeCtx) StringResult() (string, error) {
|
|
if b.isACK() {
|
|
if b.wait() != nil {
|
|
return "", b.err
|
|
}
|
|
|
|
return b.result, b.err
|
|
}
|
|
|
|
if b.isOK() {
|
|
return b.getMsg(), nil
|
|
}
|
|
|
|
return "", errors.New(b.getMsg())
|
|
}
|
|
|
|
//export callback
|
|
func callback(ret C.int, msg *C.char, len C.size_t, resp unsafe.Pointer) {
|
|
if resp == nil {
|
|
return
|
|
}
|
|
|
|
// log.Println("Callback called with ret:", ret, " msg:", C.GoStringN(msg, C.int(len)), " len:", len)
|
|
|
|
m := (*C.Resp)(resp)
|
|
m.ret = ret
|
|
m.msg = msg
|
|
m.len = len
|
|
|
|
if m.h != 0 {
|
|
h := cgo.Handle(m.h)
|
|
|
|
if h == 0 {
|
|
return
|
|
}
|
|
|
|
if v, ok := h.Value().(*bridgeCtx); ok {
|
|
if ret == C.RET_OK || ret == C.RET_ERR {
|
|
retMsg := C.GoStringN(msg, C.int(len))
|
|
|
|
if ret == C.RET_OK {
|
|
v.result = retMsg
|
|
} else {
|
|
v.err = errors.New(retMsg)
|
|
}
|
|
|
|
h.Delete()
|
|
m.h = 0
|
|
|
|
if v.wg != nil {
|
|
v.wg.Done()
|
|
v = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func CodexNew(config CodexConfig) (*CodexNode, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
jsonConfig, err := json.Marshal(config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cJsonConfig := C.CString(string(jsonConfig))
|
|
defer C.free(unsafe.Pointer(cJsonConfig))
|
|
|
|
ctx := C.cGoCodexNew(cJsonConfig, bridge.resp)
|
|
|
|
if bridge.isACK() {
|
|
if bridge.wait() != nil {
|
|
return nil, bridge.err
|
|
}
|
|
|
|
return &CodexNode{ctx: ctx}, bridge.err
|
|
}
|
|
|
|
if bridge.isOK() {
|
|
return &CodexNode{ctx: ctx}, nil
|
|
}
|
|
|
|
return nil, errors.New(bridge.getMsg())
|
|
}
|
|
|
|
func (self *CodexNode) CodexVersion() (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
C.cGoCodexVersion(self.ctx, bridge.resp)
|
|
|
|
return bridge.StringResult()
|
|
}
|
|
|
|
func (self *CodexNode) CodexRevision() (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
C.cGoCodexRevision(self.ctx, bridge.resp)
|
|
|
|
return bridge.StringResult()
|
|
}
|
|
|
|
func (self *CodexNode) CodexRepo() (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
C.cGoCodexRepo(self.ctx, bridge.resp)
|
|
|
|
return bridge.StringResult()
|
|
}
|
|
|
|
// CodexStart returns the bridgeCtx to allow the caller
|
|
// to wait for the operation to complete or not.
|
|
// TODO: be consistent and do not free the bridgeCtx here
|
|
func (self *CodexNode) CodexStart() (*bridgeCtx, error) {
|
|
bridge := newBridgeCtx()
|
|
|
|
C.cGoCodexStart(self.ctx, bridge.resp)
|
|
|
|
if bridge.isError() {
|
|
return nil, errors.New(bridge.getMsg())
|
|
}
|
|
|
|
return bridge, nil
|
|
}
|
|
|
|
// CodexStop returns the bridgeCtx to allow the caller
|
|
// to wait for the operation to complete or not.
|
|
// TODO: be consistent and do not free the bridgeCtx here
|
|
func (self *CodexNode) CodexStop() (*bridgeCtx, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
C.cGoCodexStop(self.ctx, bridge.resp)
|
|
|
|
if bridge.isError() {
|
|
return nil, errors.New(bridge.getMsg())
|
|
}
|
|
|
|
return bridge, nil
|
|
}
|
|
|
|
func (self *CodexNode) CodexDestroy() error {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
C.cGoCodexDestroy(self.ctx, bridge.resp)
|
|
|
|
if bridge.isACK() {
|
|
err := bridge.wait()
|
|
self.ctx = nil
|
|
return err
|
|
}
|
|
|
|
if bridge.isOK() {
|
|
self.ctx = nil
|
|
return nil
|
|
}
|
|
|
|
return errors.New(bridge.getMsg())
|
|
}
|
|
|
|
//export globalEventCallback
|
|
func globalEventCallback(callerRet C.int, msg *C.char, len C.size_t, userData unsafe.Pointer) {
|
|
// This is shared among all Golang instances
|
|
|
|
self := CodexNode{ctx: userData}
|
|
self.MyEventCallback(callerRet, msg, len)
|
|
}
|
|
|
|
func (self *CodexNode) MyEventCallback(callerRet C.int, msg *C.char, len C.size_t) {
|
|
log.Println("Event received:", C.GoStringN(msg, C.int(len)))
|
|
}
|
|
|
|
func (self *CodexNode) CodexSetEventCallback() {
|
|
// Notice that the events for self node are handled by the 'MyEventCallback' method
|
|
C.cGoCodexSetEventCallback(self.ctx)
|
|
}
|
|
|
|
func main() {
|
|
config := CodexConfig{
|
|
LogLevel: Info,
|
|
}
|
|
|
|
log.Println("Creating Codex...")
|
|
|
|
node, err := CodexNew(config)
|
|
if err != nil {
|
|
fmt.Println("Error happened:", err.Error())
|
|
return
|
|
}
|
|
|
|
log.Println("Codex created.")
|
|
|
|
// node.CodexSetEventCallback()
|
|
|
|
log.Println("Getting version...")
|
|
|
|
version, err := node.CodexVersion()
|
|
if err != nil {
|
|
fmt.Println("Error happened:", err.Error())
|
|
return
|
|
}
|
|
|
|
log.Println("Codex version:", version)
|
|
|
|
log.Println("Getting revision...")
|
|
|
|
revision, err := node.CodexRevision()
|
|
if err != nil {
|
|
fmt.Println("Error happened:", err.Error())
|
|
return
|
|
}
|
|
|
|
log.Println("Codex revision:", revision)
|
|
|
|
log.Println("Getting repo...")
|
|
|
|
repo, err := node.CodexRepo()
|
|
if err != nil {
|
|
fmt.Println("Error happened:", err.Error())
|
|
return
|
|
}
|
|
|
|
log.Println("Codex repo:", repo)
|
|
|
|
log.Println("Starting Codex...")
|
|
|
|
bridge, err := node.CodexStart()
|
|
|
|
if err != nil {
|
|
fmt.Println("Error happened:", err.Error())
|
|
return
|
|
}
|
|
|
|
defer bridge.free()
|
|
|
|
if err := bridge.wait(); err != nil {
|
|
fmt.Println("Error happened:", err)
|
|
return
|
|
}
|
|
|
|
log.Println("Codex started...")
|
|
|
|
// Wait for a SIGINT or SIGTERM signal
|
|
ch := make(chan os.Signal, 1)
|
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
|
<-ch
|
|
|
|
log.Println("Stopping the node...")
|
|
|
|
bridge, err = node.CodexStop()
|
|
|
|
if err != nil {
|
|
fmt.Println("Error happened:", err.Error())
|
|
return
|
|
}
|
|
|
|
defer bridge.free()
|
|
|
|
log.Println("Codex stopped...")
|
|
|
|
if err := bridge.wait(); err != nil {
|
|
fmt.Println("Error happened:", err)
|
|
return
|
|
}
|
|
|
|
log.Println("Destroying the node...")
|
|
|
|
err = node.CodexDestroy()
|
|
if err != nil {
|
|
fmt.Println("Error happened:", err.Error())
|
|
return
|
|
}
|
|
}
|