diff --git a/codex/storage.go b/codex/storage.go new file mode 100644 index 0000000..256598e --- /dev/null +++ b/codex/storage.go @@ -0,0 +1,144 @@ +package codex + +import ( + "encoding/json" + "unsafe" +) + +/* + #include "bridge.h" + #include + + static int cGoCodexStorageList(void* codexCtx, void* resp) { + return codex_storage_list(codexCtx, (CodexCallback) callback, resp); + } + + static int cGoCodexStorageFetch(void* codexCtx, char* cid, void* resp) { + return codex_storage_fetch(codexCtx, cid, (CodexCallback) callback, resp); + } + + static int cGoCodexStorageSpace(void* codexCtx, void* resp) { + return codex_storage_space(codexCtx, (CodexCallback) callback, resp); + } + + static int cGoCodexStorageDelete(void* codexCtx, char* cid, void* resp) { + return codex_storage_delete(codexCtx, cid, (CodexCallback) callback, resp); + } +*/ +import "C" + +type manifestWithCid struct { + Cid string `json:"cid"` + Manifest Manifest `json:"manifest"` +} + +type Space struct { + // TotalBlocks is the number of blocks stored by the node + TotalBlocks int `json:"totalBlocks"` + + // QuotaMaxBytes is the maximum storage space (in bytes) available + // for the node in Codex's local repository. + QuotaMaxBytes int64 `json:"quotaMaxBytes"` + + // QuotaUsedBytes is the mount of storage space (in bytes) currently used + // for storing files in Codex's local repository. + QuotaUsedBytes int64 `json:"quotaUsedBytes"` + + // QuotaReservedBytes is the amount of storage reserved (in bytes) in the + // Codex's local repository for future use when storage requests will be picked + // up and hosted by the node using node's availabilities. + // This does not include the storage currently in use. + QuotaReservedBytes int64 `json:"quotaReservedBytes"` +} + +// Manifests returns the list of all manifests stored by the Codex node. +func (node CodexNode) Manifests() ([]Manifest, error) { + bridge := newBridgeCtx() + defer bridge.free() + + if C.cGoCodexStorageList(node.ctx, bridge.resp) != C.RET_OK { + return nil, bridge.callError("cGoCodexStorageList") + } + value, err := bridge.wait() + if err != nil { + return nil, err + } + + var items []manifestWithCid + err = json.Unmarshal([]byte(value), &items) + if err != nil { + return nil, err + } + + var list []Manifest + for _, item := range items { + item.Manifest.Cid = item.Cid + list = append(list, item.Manifest) + } + + return list, err +} + +// Fetch download a file from the network and store it to the local node. +func (node CodexNode) Fetch(cid string) (Manifest, error) { + bridge := newBridgeCtx() + defer bridge.free() + + var cCid = C.CString(cid) + defer C.free(unsafe.Pointer(cCid)) + + if C.cGoCodexStorageFetch(node.ctx, cCid, bridge.resp) != C.RET_OK { + return Manifest{}, bridge.callError("cGoCodexStorageFetch") + } + + value, err := bridge.wait() + if err != nil { + return Manifest{}, err + } + + var manifest Manifest + err = json.Unmarshal([]byte(value), &manifest) + if err != nil { + return Manifest{}, err + } + + manifest.Cid = cid + return manifest, nil +} + +// Space returns information about the storage space used and available. +func (node CodexNode) Space() (Space, error) { + var space Space + + bridge := newBridgeCtx() + defer bridge.free() + + if C.cGoCodexStorageSpace(node.ctx, bridge.resp) != C.RET_OK { + return space, bridge.callError("cGoCodexStorageSpace") + } + + value, err := bridge.wait() + if err != nil { + return space, err + } + + err = json.Unmarshal([]byte(value), &space) + return space, err +} + +// Deletes either a single block or an entire dataset +// from the local node. Does nothing if the dataset is not locally available. +func (node CodexNode) Delete(cid string) error { + bridge := newBridgeCtx() + defer bridge.free() + + var cCid = C.CString(cid) + defer C.free(unsafe.Pointer(cCid)) + + if C.cGoCodexStorageDelete(node.ctx, cCid, bridge.resp) != C.RET_OK { + return bridge.callError("cGoCodexStorageDelete") + } + + _, err := bridge.wait() + return err +} diff --git a/codex/storage_test.go b/codex/storage_test.go new file mode 100644 index 0000000..38296b1 --- /dev/null +++ b/codex/storage_test.go @@ -0,0 +1,93 @@ +package codex + +import "testing" + +func TestManifests(t *testing.T) { + codex := newCodexNode(t) + + manifests, err := codex.Manifests() + if err != nil { + t.Fatal(err) + } + + if len(manifests) != 0 { + t.Fatal("expected manifests to be empty") + } + + cid, _ := uploadHelper(t, codex) + + manifests, err = codex.Manifests() + if err != nil { + t.Fatal(err) + } + + if len(manifests) == 0 { + t.Fatal("expected manifests to be non-empty") + } + + for _, m := range manifests { + if m.Cid != cid { + t.Errorf("expected cid %q, got %q", cid, m.Cid) + } + } +} + +func TestSpace(t *testing.T) { + codex := newCodexNode(t) + + space, err := codex.Space() + if err != nil { + t.Fatal(err) + } + + if space.TotalBlocks != 0 { + t.Fatal("expected total blocks to be non-zero") + } + + if space.QuotaMaxBytes == 0 { + t.Fatal("expected quota max bytes to be non-zero") + } + + if space.QuotaUsedBytes != 0 { + t.Fatal("expected quota used bytes to be non-zero") + } + + if space.QuotaReservedBytes != 0 { + t.Fatal("expected quota reserved bytes to be non-zero") + } + + uploadHelper(t, codex) + + space, err = codex.Space() + if err != nil { + t.Fatal(err) + } + + if space.TotalBlocks == 0 { + t.Fatal("expected total blocks to be non-zero after upload") + } + + if space.QuotaUsedBytes == 0 { + t.Fatal("expected quota used bytes to be non-zero after upload") + } +} + +func TestFetch(t *testing.T) { + codex := newCodexNode(t) + + cid, _ := uploadHelper(t, codex) + + _, err := codex.Fetch(cid) + if err != nil { + t.Fatal("expected error when fetching non-existent manifest") + } +} + +func TestFetchCidDoesNotExist(t *testing.T) { + codex := newCodexNode(t, withBlockRetries(1)) + + _, err := codex.Fetch("bafybeihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku") + if err == nil { + t.Fatal("expected error when fetching non-existent manifest") + } +}