package main /* #cgo LDFLAGS: -L../../build/ -lcodex #cgo LDFLAGS: -L../../ -Wl,-rpath,../../ #include "../../library/libcodex.h" #include #include #include 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 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() { if b.resp != nil { 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() { b.wg.Wait() } func (b *bridgeCtx) getMsg() string { return C.GoStringN(C.getMyCharPtr(b.resp), C.int(C.getMyCharLen(b.resp))) } //export callback func callback(ret C.int, msg *C.char, len C.size_t, resp unsafe.Pointer) { if resp == nil { return } m := (*C.Resp)(resp) m.ret = ret m.msg = msg m.len = len if m.h != 0 { h := cgo.Handle(m.h) if v, ok := h.Value().(*bridgeCtx); ok { if ret == C.RET_OK || ret == C.RET_ERR { msg := C.GoStringN(msg, C.int(len)) if ret == C.RET_OK { v.result = msg } else { v.err = errors.New(msg) } h.Delete() if v.wg != nil { v.wg.Done() } } } } } 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() { bridge.wait() 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) if bridge.isACK() { bridge.wait() return bridge.result, bridge.err } if bridge.isOK() { return bridge.getMsg(), nil } return "", errors.New(bridge.getMsg()) } func (self *CodexNode) CodexRevision() (string, error) { bridge := newBridgeCtx() defer bridge.free() C.cGoCodexRevision(self.ctx, bridge.resp) if bridge.isACK() { bridge.wait() return bridge.result, bridge.err } if bridge.isOK() { return bridge.result, nil } return "", errors.New(bridge.getMsg()) } // 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() { bridge.free() 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() C.cGoCodexStop(self.ctx, bridge.resp) if bridge.isError() { bridge.free() 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() { bridge.wait() return bridge.err } if bridge.isOK() { 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 } node.CodexSetEventCallback() version, err := node.CodexVersion() if err != nil { fmt.Println("Error happened:", err.Error()) return } log.Println("Codex version:", version) revision, err := node.CodexRevision() if err != nil { fmt.Println("Error happened:", err.Error()) return } log.Println("Codex revision:", revision) log.Println("Starting Codex...") bridge, err := node.CodexStart() if err != nil { fmt.Println("Error happened:", err.Error()) return } bridge.wait() defer bridge.free() if bridge.err != nil { fmt.Println("Error happened:", err.Error()) 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 } bridge.wait() defer bridge.free() log.Println("Codex stopped...") if bridge.err != nil { fmt.Println("Error happened:", err.Error()) return } log.Println("Destroying the node...") err = node.CodexDestroy() if err != nil { fmt.Println("Error happened:", err.Error()) return } }