mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-01-03 14:03:10 +00:00
1115 lines
26 KiB
Go
1115 lines
26 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(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 int getRet(void* resp) {
|
|
if (resp == NULL) {
|
|
return 0;
|
|
}
|
|
Resp* m = (Resp*) resp;
|
|
return m->ret;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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);
|
|
|
|
static void* cGoCodexNew(const char* configJson, void* resp) {
|
|
void* ret = codex_new(configJson, (CodexCallback) callback, resp);
|
|
return ret;
|
|
}
|
|
|
|
static int cGoCodexVersion(void* codexCtx, void* resp) {
|
|
return codex_version(codexCtx, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexRevision(void* codexCtx, void* resp) {
|
|
return codex_revision(codexCtx, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexRepo(void* codexCtx, void* resp) {
|
|
return codex_repo(codexCtx, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexDebug(void* codexCtx, void* resp) {
|
|
return codex_debug(codexCtx, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexSpr(void* codexCtx, void* resp) {
|
|
return codex_spr(codexCtx, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexPeerId(void* codexCtx, void* resp) {
|
|
return codex_peer_id(codexCtx, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexLogLevel(void* codexCtx, char* logLevel, void* resp) {
|
|
return codex_log_level(codexCtx, logLevel, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexConnect(void* codexCtx, char* peerId, const char** peerAddresses, uintptr_t peerAddressesSize, void* resp) {
|
|
return codex_connect(codexCtx, peerId, peerAddresses, peerAddressesSize, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexPeerDebug(void* codexCtx, char* peerId, void* resp) {
|
|
return codex_peer_debug(codexCtx, peerId, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexUploadInit(void* codexCtx, char* filepath, size_t chunkSize, void* resp) {
|
|
return codex_upload_init(codexCtx, filepath, chunkSize, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexUploadChunk(void* codexCtx, char* sessionId, const uint8_t* chunk, size_t len, void* resp) {
|
|
return codex_upload_chunk(codexCtx, sessionId, chunk, len, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexUploadFinalize(void* codexCtx, char* sessionId, void* resp) {
|
|
return codex_upload_finalize(codexCtx, sessionId, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexUploadCancel(void* codexCtx, char* sessionId, void* resp) {
|
|
return codex_upload_cancel(codexCtx, sessionId, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexUploadFile(void* codexCtx, char* sessionId, void* resp) {
|
|
return codex_upload_file(codexCtx, sessionId, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexDownloadInit(void* codexCtx, char* cid, size_t chunkSize, bool local, void* resp) {
|
|
return codex_download_init(codexCtx, cid, chunkSize, local, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexDownloadChunk(void* codexCtx, char* cid, void* resp) {
|
|
return codex_download_chunk(codexCtx, cid, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexDownloadLocal(void* codexCtx, char* cid, size_t chunkSize, void* resp) {
|
|
return codex_download_local(codexCtx, cid, chunkSize, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexDownloadCancel(void* codexCtx, char* cid, void* resp) {
|
|
return codex_download_cancel(codexCtx, cid, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexStart(void* codexCtx, void* resp) {
|
|
return codex_start(codexCtx, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexStop(void* codexCtx, void* resp) {
|
|
return codex_stop(codexCtx, (CodexCallback) callback, resp);
|
|
}
|
|
|
|
static int cGoCodexDestroy(void* codexCtx, void* resp) {
|
|
return 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 (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"path"
|
|
"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 {
|
|
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 RestPeerRecord struct {
|
|
PeerId string `json:"peerId"`
|
|
SeqNo int `json:"seqNo"`
|
|
Addresses []string `json:"addresses,omitempty"`
|
|
}
|
|
|
|
type RestNode struct {
|
|
NodeId string `json:"nodeId"`
|
|
PeerId string `json:"peerId"`
|
|
Record string `json:"record"`
|
|
Address *string `json:"address"`
|
|
Seen bool `json:"seen"`
|
|
}
|
|
|
|
type RestRoutingTable struct {
|
|
LocalNode RestNode `json:"localNode"`
|
|
Nodes []RestNode `json:"nodes"`
|
|
}
|
|
|
|
type CodexDebugInfo struct {
|
|
ID string `json:"id"`
|
|
Addrs []string `json:"addrs"`
|
|
Spr string `json:"spr"`
|
|
AnnounceAddresses []string `json:"announceAddresses"`
|
|
Table RestRoutingTable `json:"table"`
|
|
}
|
|
|
|
type CodexNode struct {
|
|
ctx unsafe.Pointer
|
|
}
|
|
|
|
const defaultBlockSize = 1024 * 64
|
|
|
|
type OnProgressFunc func(read, total int, percent float64, err error)
|
|
|
|
type CodexUploadOptions struct {
|
|
filepath string
|
|
chunkSize int
|
|
onProgress OnProgressFunc
|
|
}
|
|
|
|
type bridgeCtx struct {
|
|
wg *sync.WaitGroup
|
|
h cgo.Handle
|
|
resp unsafe.Pointer
|
|
result string
|
|
err error
|
|
|
|
// Callback used for upload and download
|
|
onProgress func(read int, chunk []byte)
|
|
}
|
|
|
|
func newBridgeCtx() *bridgeCtx {
|
|
bridge := &bridgeCtx{}
|
|
bridge.wg = &sync.WaitGroup{}
|
|
bridge.wg.Add(1)
|
|
bridge.h = cgo.NewHandle(bridge)
|
|
bridge.resp = C.allocResp(C.uintptr_t(uintptr(bridge.h)))
|
|
|
|
return bridge
|
|
}
|
|
|
|
func (b *bridgeCtx) free() {
|
|
if b.h > 0 {
|
|
b.h.Delete()
|
|
b.h = 0
|
|
}
|
|
|
|
if b.resp != nil {
|
|
C.freeResp(b.resp)
|
|
b.resp = nil
|
|
}
|
|
}
|
|
|
|
func (b *bridgeCtx) CallError(name string) error {
|
|
return fmt.Errorf("Failed the call to %s. Returned code: %d.", name, C.getRet(b.resp))
|
|
}
|
|
|
|
func (b *bridgeCtx) wait() (string, error) {
|
|
b.wg.Wait()
|
|
|
|
return b.result, b.err
|
|
}
|
|
|
|
func getReaderSize(r io.Reader) int64 {
|
|
switch v := r.(type) {
|
|
case *os.File:
|
|
stat, err := v.Stat()
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return stat.Size()
|
|
case *bytes.Buffer:
|
|
return int64(v.Len())
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
//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 {
|
|
return
|
|
}
|
|
|
|
h := cgo.Handle(m.h)
|
|
|
|
if h == 0 {
|
|
return
|
|
}
|
|
|
|
if v, ok := h.Value().(*bridgeCtx); ok {
|
|
switch ret {
|
|
case C.RET_PROGRESS:
|
|
if v.onProgress == nil {
|
|
return
|
|
}
|
|
|
|
if msg != nil {
|
|
chunk := C.GoBytes(unsafe.Pointer(msg), C.int(len))
|
|
v.onProgress(int(C.int(len)), chunk)
|
|
} else {
|
|
v.onProgress(int(C.int(len)), nil)
|
|
}
|
|
case C.RET_OK:
|
|
retMsg := C.GoStringN(msg, C.int(len))
|
|
v.result = retMsg
|
|
v.err = nil
|
|
|
|
if v.wg != nil {
|
|
v.wg.Done()
|
|
}
|
|
case C.RET_ERR:
|
|
retMsg := C.GoStringN(msg, C.int(len))
|
|
v.err = errors.New(retMsg)
|
|
|
|
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 _, err := bridge.wait(); err != nil {
|
|
return nil, bridge.err
|
|
}
|
|
|
|
return &CodexNode{ctx: ctx}, bridge.err
|
|
}
|
|
|
|
func (self CodexNode) CodexVersion() (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
if C.cGoCodexVersion(self.ctx, bridge.resp) != C.RET_OK {
|
|
return "", bridge.CallError("cGoCodexVersion")
|
|
}
|
|
|
|
return bridge.wait()
|
|
}
|
|
|
|
func (self CodexNode) CodexRevision() (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
if C.cGoCodexRevision(self.ctx, bridge.resp) != C.RET_OK {
|
|
return "", bridge.CallError("cGoCodexRevision")
|
|
}
|
|
|
|
return bridge.wait()
|
|
}
|
|
|
|
func (self CodexNode) CodexRepo() (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
if C.cGoCodexRepo(self.ctx, bridge.resp) != C.RET_OK {
|
|
return "", bridge.CallError("cGoCodexRepo")
|
|
}
|
|
|
|
return bridge.wait()
|
|
}
|
|
|
|
func (self CodexNode) CodexDebug() (CodexDebugInfo, error) {
|
|
var info CodexDebugInfo
|
|
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
if C.cGoCodexDebug(self.ctx, bridge.resp) != C.RET_OK {
|
|
return info, bridge.CallError("cGoCodexDebug")
|
|
}
|
|
|
|
value, err := bridge.wait()
|
|
if err != nil {
|
|
return info, err
|
|
}
|
|
|
|
err = json.Unmarshal([]byte(value), &info)
|
|
|
|
return info, err
|
|
}
|
|
|
|
func (self CodexNode) CodexSpr() (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
if C.cGoCodexSpr(self.ctx, bridge.resp) != C.RET_OK {
|
|
return "", bridge.CallError("cGoCodexSpr")
|
|
}
|
|
|
|
return bridge.wait()
|
|
}
|
|
|
|
func (self CodexNode) CodexPeerId() (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
if C.cGoCodexPeerId(self.ctx, bridge.resp) != C.RET_OK {
|
|
return "", bridge.CallError("cGoCodexPeerId")
|
|
}
|
|
|
|
return bridge.wait()
|
|
}
|
|
|
|
func (self CodexNode) CodexLogLevel(logLevel LogLevel) error {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
var cLogLevel = C.CString(fmt.Sprintf("%s", logLevel))
|
|
defer C.free(unsafe.Pointer(cLogLevel))
|
|
|
|
if C.cGoCodexLogLevel(self.ctx, cLogLevel, bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexLogLevel")
|
|
}
|
|
|
|
_, err := bridge.wait()
|
|
|
|
return err
|
|
}
|
|
|
|
func (self CodexNode) CodexConnect(peerId string, peerAddresses []string) error {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
var cPeerId = C.CString(peerId)
|
|
defer C.free(unsafe.Pointer(cPeerId))
|
|
|
|
if len(peerAddresses) > 0 {
|
|
var cAddresses = make([]*C.char, len(peerAddresses))
|
|
for i, addr := range peerAddresses {
|
|
cAddresses[i] = C.CString(addr)
|
|
defer C.free(unsafe.Pointer(cAddresses[i]))
|
|
}
|
|
|
|
if C.cGoCodexConnect(self.ctx, cPeerId, &cAddresses[0], C.uintptr_t(len(peerAddresses)), bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexConnect")
|
|
}
|
|
} else {
|
|
if C.cGoCodexConnect(self.ctx, cPeerId, nil, 0, bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexConnect")
|
|
}
|
|
}
|
|
|
|
_, err := bridge.wait()
|
|
return err
|
|
}
|
|
|
|
func (self CodexNode) CodexPeerDebug(peerId string) (RestPeerRecord, error) {
|
|
var record RestPeerRecord
|
|
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
var cPeerId = C.CString(peerId)
|
|
defer C.free(unsafe.Pointer(cPeerId))
|
|
|
|
if C.cGoCodexPeerDebug(self.ctx, cPeerId, bridge.resp) != C.RET_OK {
|
|
return record, bridge.CallError("cGoCodexPeerDebug")
|
|
}
|
|
|
|
value, err := bridge.wait()
|
|
if err != nil {
|
|
return record, err
|
|
}
|
|
|
|
err = json.Unmarshal([]byte(value), &record)
|
|
|
|
return record, err
|
|
}
|
|
|
|
func (self CodexNode) CodexUploadInit(options *CodexUploadOptions) (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
var cFilename = C.CString(options.filepath)
|
|
defer C.free(unsafe.Pointer(cFilename))
|
|
|
|
if options.chunkSize == 0 {
|
|
options.chunkSize = defaultBlockSize
|
|
}
|
|
var cChunkSize = C.size_t(options.chunkSize)
|
|
|
|
if C.cGoCodexUploadInit(self.ctx, cFilename, cChunkSize, bridge.resp) != C.RET_OK {
|
|
return "", bridge.CallError("cGoCodexUploadInit")
|
|
}
|
|
|
|
return bridge.wait()
|
|
}
|
|
|
|
func (self CodexNode) CodexUploadChunk(sessionId string, chunk []byte) error {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
var cSessionId = C.CString(sessionId)
|
|
defer C.free(unsafe.Pointer(cSessionId))
|
|
|
|
var cChunkPtr *C.uint8_t
|
|
if len(chunk) > 0 {
|
|
cChunkPtr = (*C.uint8_t)(unsafe.Pointer(&chunk[0]))
|
|
}
|
|
|
|
if C.cGoCodexUploadChunk(self.ctx, cSessionId, cChunkPtr, C.size_t(len(chunk)), bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexUploadChunk")
|
|
}
|
|
|
|
_, err := bridge.wait()
|
|
|
|
return err
|
|
}
|
|
|
|
func (self CodexNode) CodexUploadFinalize(sessionId string) (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
var cSessionId = C.CString(sessionId)
|
|
defer C.free(unsafe.Pointer(cSessionId))
|
|
|
|
if C.cGoCodexUploadFinalize(self.ctx, cSessionId, bridge.resp) != C.RET_OK {
|
|
return "", bridge.CallError("cGoCodexUploadFinalize")
|
|
}
|
|
|
|
return bridge.wait()
|
|
}
|
|
|
|
func (self CodexNode) CodexUploadCancel(sessionId string) error {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
var cSessionId = C.CString(sessionId)
|
|
defer C.free(unsafe.Pointer(cSessionId))
|
|
|
|
if C.cGoCodexUploadCancel(self.ctx, cSessionId, bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexUploadCancel")
|
|
}
|
|
|
|
_, err := bridge.wait()
|
|
|
|
return err
|
|
}
|
|
|
|
func (self CodexNode) CodexUploadReader(options CodexUploadOptions, r io.Reader) (string, error) {
|
|
sessionId, err := self.CodexUploadInit(&options)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if options.chunkSize == 0 {
|
|
options.chunkSize = defaultBlockSize
|
|
}
|
|
|
|
buf := make([]byte, options.chunkSize)
|
|
total := 0
|
|
var size int64
|
|
|
|
if options.onProgress != nil {
|
|
size = getReaderSize(r)
|
|
}
|
|
|
|
for {
|
|
n, err := r.Read(buf)
|
|
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
if err != nil {
|
|
if cancelErr := self.CodexUploadCancel(sessionId); cancelErr != nil {
|
|
return "", fmt.Errorf("failed to upload chunk %v and failed to cancel upload session %v", err, cancelErr)
|
|
}
|
|
|
|
return "", err
|
|
}
|
|
|
|
if n == 0 {
|
|
break
|
|
}
|
|
|
|
if err := self.CodexUploadChunk(sessionId, buf[:n]); err != nil {
|
|
if cancelErr := self.CodexUploadCancel(sessionId); cancelErr != nil {
|
|
return "", fmt.Errorf("failed to upload chunk %v and failed to cancel upload session %v", err, cancelErr)
|
|
}
|
|
|
|
return "", err
|
|
}
|
|
|
|
total += n
|
|
if options.onProgress != nil && size > 0 {
|
|
percent := float64(total) / float64(size) * 100.0
|
|
// The last block could be a bit over the size due to padding
|
|
// on the chunk size.
|
|
if percent > 100.0 {
|
|
percent = 100.0
|
|
}
|
|
options.onProgress(n, total, percent, nil)
|
|
}
|
|
}
|
|
|
|
return self.CodexUploadFinalize(sessionId)
|
|
}
|
|
|
|
func (self CodexNode) CodexUploadReaderAsync(options CodexUploadOptions, r io.Reader, onDone func(cid string, err error)) {
|
|
go func() {
|
|
cid, err := self.CodexUploadReader(options, r)
|
|
onDone(cid, err)
|
|
}()
|
|
}
|
|
|
|
func (self CodexNode) CodexUploadFile(options CodexUploadOptions) (string, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
if options.onProgress != nil {
|
|
stat, err := os.Stat(options.filepath)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
size := stat.Size()
|
|
total := 0
|
|
|
|
if size > 0 {
|
|
bridge.onProgress = func(read int, _ []byte) {
|
|
if read == 0 {
|
|
return
|
|
}
|
|
|
|
total += read
|
|
|
|
percent := float64(total) / float64(size) * 100.0
|
|
// The last block could be a bit over the size due to padding
|
|
// on the chunk size.
|
|
if percent > 100.0 {
|
|
percent = 100.0
|
|
}
|
|
|
|
options.onProgress(read, int(size), percent, nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
sessionId, err := self.CodexUploadInit(&options)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var cSessionId = C.CString(sessionId)
|
|
defer C.free(unsafe.Pointer(cSessionId))
|
|
|
|
if C.cGoCodexUploadFile(self.ctx, cSessionId, bridge.resp) != C.RET_OK {
|
|
return "", bridge.CallError("cGoCodexUploadFile")
|
|
}
|
|
|
|
return bridge.wait()
|
|
}
|
|
|
|
func (self CodexNode) CodexUploadFileAsync(options CodexUploadOptions, onDone func(cid string, err error)) {
|
|
go func() {
|
|
cid, err := self.CodexUploadFile(options)
|
|
onDone(cid, err)
|
|
}()
|
|
}
|
|
|
|
func (self CodexNode) CodexDownloadLocal(cid string, chunkSize int, w io.Writer) error {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
bridge.onProgress = func(read int, chunk []byte) {
|
|
if read == 0 {
|
|
return
|
|
}
|
|
|
|
if _, err := w.Write(chunk); err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
|
|
var cCid = C.CString(cid)
|
|
defer C.free(unsafe.Pointer(cCid))
|
|
|
|
if chunkSize == 0 {
|
|
chunkSize = defaultBlockSize
|
|
}
|
|
var cChunkSize = C.size_t(chunkSize)
|
|
|
|
if C.cGoCodexDownloadLocal(self.ctx, cCid, cChunkSize, bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexDownloadLocal")
|
|
}
|
|
|
|
_, err := bridge.wait()
|
|
|
|
return err
|
|
}
|
|
|
|
func (self CodexNode) CodexDownloadLocalAsync(cid string, chunkSize int, w io.Writer, onDone func(error)) {
|
|
go func() {
|
|
err := self.CodexDownloadLocal(cid, chunkSize, w)
|
|
onDone(err)
|
|
}()
|
|
}
|
|
|
|
func (self CodexNode) CodexDownloadInit(cid string, chunkSize int, local bool) error {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
var cCid = C.CString(cid)
|
|
defer C.free(unsafe.Pointer(cCid))
|
|
|
|
if chunkSize == 0 {
|
|
chunkSize = defaultBlockSize
|
|
}
|
|
var cChunkSize = C.size_t(chunkSize)
|
|
var cLocal = C.bool(local)
|
|
|
|
if C.cGoCodexDownloadInit(self.ctx, cCid, cChunkSize, cLocal, bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexDownloadInit")
|
|
}
|
|
|
|
_, err := bridge.wait()
|
|
|
|
return err
|
|
}
|
|
|
|
func (self CodexNode) CodexDownloadChunk(cid string) ([]byte, error) {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
var bytes []byte
|
|
|
|
bridge.onProgress = func(read int, chunk []byte) {
|
|
bytes = chunk
|
|
}
|
|
|
|
var cCid = C.CString(cid)
|
|
defer C.free(unsafe.Pointer(cCid))
|
|
|
|
if C.cGoCodexDownloadChunk(self.ctx, cCid, bridge.resp) != C.RET_OK {
|
|
return nil, bridge.CallError("cGoCodexDownloadChunk")
|
|
}
|
|
|
|
if _, err := bridge.wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return bytes, nil
|
|
}
|
|
|
|
func (self CodexNode) CodexDownloadCancel(cid string) error {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
var cCid = C.CString(cid)
|
|
defer C.free(unsafe.Pointer(cCid))
|
|
|
|
if C.cGoCodexDownloadCancel(self.ctx, cCid, bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexDownloadCancel")
|
|
}
|
|
|
|
_, err := bridge.wait()
|
|
|
|
return err
|
|
}
|
|
|
|
func (self CodexNode) CodexStart() error {
|
|
bridge := newBridgeCtx()
|
|
defer bridge.free()
|
|
|
|
if C.cGoCodexStart(self.ctx, bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexStart")
|
|
}
|
|
|
|
_, err := bridge.wait()
|
|
|
|
return err
|
|
}
|
|
|
|
func (self CodexNode) CodexStartAsync(onDone func(error)) {
|
|
go func() {
|
|
err := self.CodexStart()
|
|
onDone(err)
|
|
}()
|
|
}
|
|
|
|
func (self CodexNode) CodexStop() error {
|
|
bridge := newBridgeCtx()
|
|
|
|
if C.cGoCodexStop(self.ctx, bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexStop")
|
|
}
|
|
|
|
_, err := bridge.wait()
|
|
return err
|
|
}
|
|
|
|
func (self CodexNode) CodexDestroy() error {
|
|
bridge := newBridgeCtx()
|
|
|
|
if C.cGoCodexDestroy(self.ctx, bridge.resp) != C.RET_OK {
|
|
return bridge.CallError("cGoCodexDestroy")
|
|
}
|
|
|
|
_, err := bridge.wait()
|
|
return err
|
|
}
|
|
|
|
//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{}
|
|
|
|
node, err := CodexNew(config)
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex created.")
|
|
|
|
node.CodexSetEventCallback()
|
|
|
|
version, err := node.CodexVersion()
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex version:", version)
|
|
|
|
revision, err := node.CodexRevision()
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex revision:", revision)
|
|
|
|
repo, err := node.CodexRepo()
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex repo:", repo)
|
|
|
|
log.Println("Starting Codex...")
|
|
|
|
err = node.CodexStart()
|
|
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex started...")
|
|
|
|
// for i := 0; i < 150; i++ {
|
|
|
|
debug, err := node.CodexDebug()
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
pretty, err := json.MarshalIndent(debug, "", " ")
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println(string(pretty))
|
|
|
|
spr, err := node.CodexSpr()
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex SPR:", spr)
|
|
|
|
peerId, err := node.CodexPeerId()
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex Peer Id:", peerId)
|
|
|
|
err = node.CodexLogLevel(Trace)
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex Log Level set to TRACE")
|
|
|
|
sessionId, err := node.CodexUploadInit(&CodexUploadOptions{filepath: "hello.txt"})
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
log.Println("Codex Upload Init sessionId:", sessionId)
|
|
|
|
err = node.CodexUploadChunk(sessionId, []byte("Hello "))
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
err = node.CodexUploadChunk(sessionId, []byte("World!"))
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
cid, err := node.CodexUploadFinalize(sessionId)
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex Upload Finalized, cid:", cid)
|
|
|
|
buf := bytes.NewBuffer([]byte("Hello World!"))
|
|
cid, err = node.CodexUploadReader(CodexUploadOptions{filepath: "hello.txt", onProgress: func(read, total int, percent float64, err error) {
|
|
if err != nil {
|
|
log.Fatal("Error happened during upload: %v\n", err)
|
|
}
|
|
|
|
log.Printf("Uploaded %d bytes, total %d bytes (%.2f%%)\n", read, total, percent)
|
|
}}, buf)
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex Upload Finalized from reader, cid:", cid)
|
|
|
|
current, err := os.Getwd()
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
// Choose a big file to see the progress logs
|
|
filepath := path.Join(current, "examples", "golang", "hello.txt")
|
|
//filepath := path.Join(current, "examples", "golang", "discord-0.0.109.deb")
|
|
|
|
options := CodexUploadOptions{filepath: filepath, onProgress: func(read, total int, percent float64, err error) {
|
|
if err != nil {
|
|
log.Fatal("Error happened during upload: %v\n", err)
|
|
}
|
|
|
|
log.Printf("Uploaded %d bytes, total %d bytes (%.2f%%)\n", read, total, percent)
|
|
}}
|
|
|
|
cid, err = node.CodexUploadFile(options)
|
|
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex Upload File finalized, cid: .", cid)
|
|
|
|
f, err := os.Create("hello.loaded.txt")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
// log.Println("Codex Download Local starting... attempt", i+1)
|
|
|
|
if err := node.CodexDownloadLocal(cid, 0, f); err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex Download Local finished.")
|
|
|
|
// log.Println("Codex Download Init starting... attempt", i+1)
|
|
|
|
if err := node.CodexDownloadInit(cid, 0, true); err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex Download Init finished.")
|
|
|
|
// log.Println("Codex Download Chunk starting... attempt", i+1)
|
|
|
|
chunk, err := node.CodexDownloadChunk(cid)
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex Download Chunk finished. Size:", len(chunk))
|
|
// }
|
|
|
|
// err = node.CodexConnect(peerId, []string{})
|
|
// if err != nil {
|
|
// log.Fatal("Error happened:", err.Error())
|
|
// }
|
|
|
|
// log.Println("Codex connecting to self...")
|
|
|
|
// val, err := node.CodexPeerDebug(peerId)
|
|
// if err != nil {
|
|
// log.Fatal("Error happened:", err.Error())
|
|
// }
|
|
|
|
// log.Println("Codex debugging self...", val)
|
|
|
|
// 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...")
|
|
|
|
err = node.CodexStop()
|
|
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
|
|
log.Println("Codex stopped...")
|
|
|
|
log.Println("Destroying the node...")
|
|
|
|
err = node.CodexDestroy()
|
|
if err != nil {
|
|
log.Fatal("Error happened:", err.Error())
|
|
}
|
|
}
|