diff --git a/.github/workflows/coverge.yml b/.github/workflows/coverge.yml index 0a2646a5..f5780cda 100644 --- a/.github/workflows/coverge.yml +++ b/.github/workflows/coverge.yml @@ -15,7 +15,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v4 with: - go-version: '1.20.1' + go-version: '1.21.4' - name: Checkout uses: actions/checkout@v3 with: @@ -24,6 +24,6 @@ jobs: env: DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }} run: | - go test -coverprofile=cover.out $(go list ./... | grep -v wasm) + go test -coverprofile=cover.out $(go list ./... | grep -v wasm) -timeout=30m curl https://deepsource.io/cli | sh ./bin/deepsource report --analyzer test-coverage --key go --value-file ./cover.out \ No newline at end of file diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ec179c1d..15baaa60 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - go: ['1.20.1'] + go: ['1.21.4'] os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Setup Go diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 695c3278..615666a6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,7 +18,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v4 with: - go-version: '1.20.1' + go-version: '1.21.4' - name: Checkout uses: actions/checkout@v3 with: @@ -45,7 +45,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v4 with: - go-version: '1.20.1' + go-version: '1.21.4' - name: Checkout uses: actions/checkout@v3 with: @@ -70,6 +70,9 @@ jobs: - name: Build android aar run: | make android + - name: Git checkout + run: | + git checkout . - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: @@ -85,7 +88,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v4 with: - go-version: '1.20.1' + go-version: '1.21.4' - name: Checkout uses: actions/checkout@v3 with: diff --git a/Dockerfile b/Dockerfile index a36654da..f7b8190c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.19-alpine AS build +FROM golang:1.21.4-alpine AS build WORKDIR /usr/fairos COPY go.mod go.sum /usr/fairos/ @@ -9,7 +9,7 @@ COPY . /usr/fairos/ RUN apk add --update --no-cache make gcc git musl-dev libc-dev linux-headers bash \ && make binary -FROM alpine:3.15 +FROM alpine:3.18 ARG CONFIG ENV CONFIG=$CONFIG diff --git a/Makefile b/Makefile index 4efc3efa..51e30170 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ GO ?= go GOLANGCI_LINT ?= $$($(GO) env GOPATH)/bin/golangci-lint -GOLANGCI_LINT_VERSION ?= v1.52.2 +GOLANGCI_LINT_VERSION ?= v1.55.2 GOGOPROTOBUF ?= protoc-gen-gogofaster GOGOPROTOBUF_VERSION ?= v1.3.1 @@ -41,11 +41,11 @@ vet: .PHONY: test-race test-race: - $(GO) test -race -timeout 300000ms -v "$(DEST)" + $(GO) test -race -timeout 30m -v "$(DEST)" .PHONY: test test: - $(GO) test -v "$(DEST)" + $(GO) test -v "$(DEST)" -timeout=30m .PHONY: githooks githooks: @@ -74,7 +74,7 @@ release: -v `pwd`:/go/src/github.com/fairDataSociety/fairOS-dfs \ -v /var/run/docker.sock:/var/run/docker.sock \ -w /go/src/github.com/fairDataSociety/fairOS-dfs \ - ghcr.io/goreleaser/goreleaser-cross:v1.20.2 release --rm-dist + ghcr.io/goreleaser/goreleaser-cross:v1.21.0 release --rm-dist .PHONY: release-dry-run release-dry-run: @@ -84,7 +84,7 @@ release-dry-run: -v `pwd`:/go/src/github.com/fairDataSociety/fairOS-dfs \ -v /var/run/docker.sock:/var/run/docker.sock \ -w /go/src/github.com/fairDataSociety/fairOS-dfs \ - ghcr.io/goreleaser/goreleaser-cross:v1.20.2 release --rm-dist \ + ghcr.io/goreleaser/goreleaser-cross:v1.21.0 release --rm-dist \ --skip-validate=true \ --skip-publish diff --git a/README.md b/README.md index 3bd0ff69..ced12eef 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,14 @@ the blockchain as ens record. whenever a user created a pod for himself, a new k is created using this mnemonic. ## What is a pod? -A pod is a personal drive created by a user in fairOS-dfs. It is used to store files and related metadata in a decentralised fashion. A pod is always under the control of the user who created it. A user can create and store any number of files or directories in a pod. +A pod is a personal drive created by a user. It is used to store files and related metadata in a decentralised fashion. A pod is always under the control of the user who created it. A user can create and store any number of files or directories in a pod. The user can share files in his pod with any other user just like in other centralised drives like dropbox. Not only users, a pod can be used by decentralised applications (DApp's) to store data related to that user. Pod creation is cheap. A user can create multiple pods and use it to organise his data. for ex: Personal-Pod, Applications-Pod etc. +## (NEW) What is a group? +A group is a shared drive created by a user. It is basically a pod, but on steroids. Group Owner can add members and update permissions. Members with "write" permission can create and store any number of files or directories in a group. + ## How to run FairOS-dfs? Run the following command to download the latest release @@ -197,4 +200,4 @@ network: "testnet" bee: bee-api-endpoint: http://localhost:1633 # bee running on mainnet postage-batch-id: -``` +``` \ No newline at end of file diff --git a/cmd/common/request.go b/cmd/common/request.go index 8a61b308..1d70f451 100644 --- a/cmd/common/request.go +++ b/cmd/common/request.go @@ -53,6 +53,7 @@ type PodReceiveRequest struct { // FileSystemRequest is the request body for file system operations type FileSystemRequest struct { PodName string `json:"podName,omitempty"` + GroupName string `json:"groupName,omitempty"` DirectoryPath string `json:"dirPath,omitempty"` DirectoryName string `json:"dirName,omitempty"` FilePath string `json:"filePath,omitempty"` @@ -62,9 +63,10 @@ type FileSystemRequest struct { // RenameRequest is the request body for file rename type RenameRequest struct { - PodName string `json:"podName,omitempty"` - OldPath string `json:"oldPath,omitempty"` - NewPath string `json:"newPath,omitempty"` + PodName string `json:"podName,omitempty"` + GroupName string `json:"groupName,omitempty"` + OldPath string `json:"oldPath,omitempty"` + NewPath string `json:"newPath,omitempty"` } // FileReceiveRequest is the request body for file receiving diff --git a/cmd/dfs-cli/cmd/fdfs-api.go b/cmd/dfs-cli/cmd/fdfs-api.go index 5deb940a..8f48d963 100644 --- a/cmd/dfs-cli/cmd/fdfs-api.go +++ b/cmd/dfs-cli/cmd/fdfs-api.go @@ -42,9 +42,9 @@ const ( // fdfsClient is the http client for dfs type fdfsClient struct { - url string - client *http.Client - cookie *http.Cookie + url string + client *http.Client + accessToken string } func newFdfsClient(fdfsServer string) (*fdfsClient, error) { @@ -97,6 +97,14 @@ func (s *fdfsClient) CheckConnection() bool { return err == nil } +func (s *fdfsClient) setAccessToken(token string) { + s.accessToken = token +} + +func (s *fdfsClient) getAccessToken() string { + return s.accessToken +} + func (s *fdfsClient) postReq(method, urlPath string, jsonBytes []byte) ([]byte, error) { // prepare the request fullUrl := fmt.Sprintf(s.url + urlPath) @@ -118,8 +126,8 @@ func (s *fdfsClient) postReq(method, urlPath string, jsonBytes []byte) ([]byte, } } - if s.cookie != nil { - req.AddCookie(s.cookie) + if s.getAccessToken() != "" { + req.Header.Add("Authorization", "Bearer "+s.getAccessToken()) } // execute the request response, err := s.client.Do(req) @@ -150,10 +158,6 @@ func (s *fdfsClient) postReq(method, urlPath string, jsonBytes []byte) ([]byte, return nil, errors.New(resp.Message) } - if len(response.Cookies()) > 0 { - s.cookie = response.Cookies()[0] - } - data, err := io.ReadAll(response.Body) if err != nil { return nil, errors.New("error downloading data") @@ -190,8 +194,8 @@ func (s *fdfsClient) getReq(urlPath, argsString string) ([]byte, error) { } } - if s.cookie != nil { - req.AddCookie(s.cookie) + if s.getAccessToken() != "" { + req.Header.Add("Authorization", "Bearer "+s.getAccessToken()) } // execute the request @@ -219,10 +223,6 @@ func (s *fdfsClient) getReq(urlPath, argsString string) ([]byte, error) { return nil, errors.New(resp.Message) } - if len(response.Cookies()) > 0 { - s.cookie = response.Cookies()[0] - } - data, err := io.ReadAll(response.Body) if err != nil { return nil, errors.New("error downloading data") @@ -283,8 +283,8 @@ func (s *fdfsClient) uploadMultipartFile(urlPath, fileName string, fileSize int6 req.Header.Set(api.CompressionHeader, compValue) } - if s.cookie != nil { - req.AddCookie(s.cookie) + if s.getAccessToken() != "" { + req.Header.Add("Authorization", "Bearer "+s.getAccessToken()) } // execute the request @@ -338,8 +338,8 @@ func (s *fdfsClient) downloadMultipartFile(method, urlPath string, arguments map req.Header.Add("Content-Type", contentType) req.Header.Add("Content-Length", strconv.Itoa(len(body.Bytes()))) - if s.cookie != nil { - req.AddCookie(s.cookie) + if s.getAccessToken() != "" { + req.Header.Add("Authorization", "Bearer "+s.getAccessToken()) } // execute the request diff --git a/cmd/dfs-cli/cmd/prompt.go b/cmd/dfs-cli/cmd/prompt.go index 979f78d7..c06d0caa 100644 --- a/cmd/dfs-cli/cmd/prompt.go +++ b/cmd/dfs-cli/cmd/prompt.go @@ -123,10 +123,19 @@ func initPrompt() { prompt.OptionPrefix(currentPrompt), prompt.OptionLivePrefix(changeLivePrefix), prompt.OptionTitle("dfs"), + prompt.OptionSetExitCheckerOnInput(exitChecker), ) p.Run() } +func exitChecker(in string, breakline bool) bool { + if breakline && strings.TrimSpace(in) == "exit" { + fmt.Println("exiting dfs-cli") + return true + } + return false +} + func changeLivePrefix() (string, bool) { return currentPrompt, true } @@ -261,7 +270,6 @@ func executor(in string) { case "help": help() case "exit": - os.Exit(0) case "user": if len(blocks) < 2 { log.Println("invalid command.") diff --git a/cmd/dfs-cli/cmd/user.go b/cmd/dfs-cli/cmd/user.go index 4f990111..3301baf8 100644 --- a/cmd/dfs-cli/cmd/user.go +++ b/cmd/dfs-cli/cmd/user.go @@ -54,7 +54,7 @@ func userNew(userName, mnemonic string) { } if resp.Message == eth.ErrInsufficientBalance.Error() { fmt.Println("Failed to create new user") - fmt.Println("Please fund your account with some eth and try again with the following command") + fmt.Println("Please fund your account with some eth and try again with the following command. This is not related with bee wallet") fmt.Printf(">>> user new %s %s\n", userName, resp.Mnemonic) fmt.Println("address :", resp.Address) fmt.Println("=============== Mnemonic ==========================") @@ -66,6 +66,9 @@ func userNew(userName, mnemonic string) { fmt.Println("Please store the 12 words mnemonic safely") fmt.Println("if you loose that, you cannot recover the data in-case of an emergency.") fmt.Println("you can also use that mnemonic to access the data in-case this device is lost") + + fdfsAPI.setAccessToken(resp.AccessToken) + currentUser = userName } @@ -85,8 +88,17 @@ func userLogin(userName, apiEndpoint string) { fmt.Println("login user: ", err) return } + var resp api.UserSignupResponse + err = json.Unmarshal(data, &resp) + if err != nil { + fmt.Println("create user: ", err) + return + } + currentUser = userName message := strings.ReplaceAll(string(data), "\n", "") + fdfsAPI.setAccessToken(resp.AccessToken) + fmt.Println(message) } diff --git a/cmd/dfs/cmd/commands_test.go b/cmd/dfs/cmd/commands_test.go index 270a2315..ec79aa34 100644 --- a/cmd/dfs/cmd/commands_test.go +++ b/cmd/dfs/cmd/commands_test.go @@ -122,8 +122,6 @@ func Test_ExecuteCommand(t *testing.T) { rootCmd.SetArgs([]string{ "server", - "--ens-network", - "play", "--beeHost", "http://localhost:1633", "--rpc", diff --git a/cmd/dfs/cmd/config.go b/cmd/dfs/cmd/config.go index a6ff98fd..9f8c85f1 100644 --- a/cmd/dfs/cmd/config.go +++ b/cmd/dfs/cmd/config.go @@ -12,6 +12,8 @@ var ( optionVerbosity = "verbosity" optionBeeApi = "bee.bee-api-endpoint" optionBeePostageBatchId = "bee.postage-batch-id" + optionFeedCacheSize = "feed.cache-size" + optionFeedCacheTTL = "feed.cache-ttl" optionCookieDomain = "cookie-domain" optionNetwork = "ens-network" optionRPC = "rpc" diff --git a/cmd/dfs/cmd/dev.go b/cmd/dfs/cmd/dev.go new file mode 100644 index 00000000..c9221a8b --- /dev/null +++ b/cmd/dfs/cmd/dev.go @@ -0,0 +1,77 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" + "testing" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/api" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/dfs" + mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/ensm/eth/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/user" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var devCmd = &cobra.Command{ + Use: "dev", + Short: "run fairOS-dfs in development mode", + Run: func(cmd *cobra.Command, args []string) { + startDevServer() + }, +} + +func init() { + rootCmd.AddCommand(devCmd) +} + +func startDevServer() { + storer := mockstorer.New() + t := &testing.T{} + fmt.Println(`▓█████▄ ▓█████ ██▒ █▓ +▒██▀ ██▌▓█ ▀▓██░ █▒ +░██ █▌▒███ ▓██ █▒░ +░▓█▄ ▌▒▓█ ▄ ▒██ █░░ +░▒████▓ ░▒████▒ ▒▀█░ + ▒▒▓ ▒ ░░ ▒░ ░ ░ ▐░ + ░ ▒ ▒ ░ ░ ░ ░ ░░ + ░ ░ ░ ░ ░░ + ░ ░ ░ ░ + ░ ░ `) + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + fmt.Println("Bee running at: ", beeUrl) + logger := logging.New(os.Stdout, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + ens := mock2.NewMockNamespaceManager() + + users := user.NewUsers(mockClient, ens, -1, 0, logger) + dfsApi := dfs.NewMockDfsAPI(mockClient, users, logger) + handler = api.NewMockHandler(dfsApi, logger, []string{"http://localhost:3000"}) + defer handler.Close() + httpPort = ":9090" + pprofPort = ":9091" + srv := startHttpService(logger) + fmt.Printf("Server running at:http://127.0.0.1%s\n", httpPort) + defer func() { + err := srv.Shutdown(context.TODO()) + if err != nil { + logger.Error("failed to shutdown server", err.Error()) + } + }() + done := make(chan os.Signal, 1) + signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) + + <-done +} diff --git a/cmd/dfs/cmd/root.go b/cmd/dfs/cmd/root.go index e93bfa6f..e7bfe5d3 100644 --- a/cmd/dfs/cmd/root.go +++ b/cmd/dfs/cmd/root.go @@ -165,7 +165,7 @@ func writeConfig() { c.Set(optionCookieDomain, defaultCookieDomain) if err := c.WriteConfigAs(cfgFile); err != nil { - fmt.Println("failed to write config file") + fmt.Println("failed to write config file", err.Error()) os.Exit(1) } } diff --git a/cmd/dfs/cmd/server.go b/cmd/dfs/cmd/server.go index 059b73fe..7d32ee07 100644 --- a/cmd/dfs/cmd/server.go +++ b/cmd/dfs/cmd/server.go @@ -80,6 +80,15 @@ can consume it.`, if err := config.BindPFlag(optionDFSPprofPort, cmd.Flags().Lookup("pprofPort")); err != nil { return err } + if err := config.BindPFlag(optionFeedCacheSize, cmd.Flags().Lookup("feedCacheSize")); err != nil { + return err + } + if err := config.BindPFlag(optionFeedCacheTTL, cmd.Flags().Lookup("feedCacheTTL")); err != nil { + return err + } + if err := config.BindPFlag(optionDFSPprofPort, cmd.Flags().Lookup("pprofPort")); err != nil { + return err + } if err := config.BindPFlag(optionCookieDomain, cmd.Flags().Lookup("cookieDomain")); err != nil { return err } @@ -124,12 +133,15 @@ can consume it.`, } ensConfig := &contracts.ENSConfig{} var subscriptionConfig *contracts.SubscriptionConfig - network := config.GetString("ens-network") + rpc := config.GetString(optionRPC) if rpc == "" { fmt.Println("\nrpc endpoint is missing") return fmt.Errorf("rpc endpoint is missing") } + + network := config.GetString(optionNetwork) + switch v := strings.ToLower(network); v { case "mainnet": fmt.Println("\nens is not available for mainnet yet") @@ -172,14 +184,28 @@ can consume it.`, logger.Info("verbosity : ", verbosity) logger.Info("httpPort : ", httpPort) logger.Info("pprofPort : ", pprofPort) + logger.Info("pprofPort : ", pprofPort) + logger.Info("pprofPort : ", pprofPort) logger.Info("cookieDomain : ", cookieDomain) - logger.Info("postageBlockId : ", postageBlockId) - logger.Info("corsOrigins : ", corsOrigins) + logger.Info("feedCacheSize : ", config.GetInt(optionFeedCacheSize)) + logger.Info("feedCacheTTL : ", config.GetString(optionFeedCacheTTL)) ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() - hdlr, err := api.New(ctx, beeApi, cookieDomain, postageBlockId, corsOrigins, ensConfig, nil, logger) + opts := &api.Options{ + BeeApiEndpoint: beeApi, + CookieDomain: cookieDomain, + Stamp: postageBlockId, + WhitelistedOrigins: corsOrigins, + EnsConfig: ensConfig, + SubscriptionConfig: subscriptionConfig, + Logger: logger, + FeedCacheSize: config.GetInt(optionFeedCacheSize), + FeedCacheTTL: config.GetString(optionFeedCacheTTL), + } + + hdlr, err := api.New(ctx, opts) if err != nil { logger.Error(err.Error()) return err @@ -216,6 +242,8 @@ func init() { serverCmd.Flags().BoolVar(&swag, "swag", false, "should run swagger-ui") serverCmd.Flags().String("httpPort", defaultDFSHttpPort, "http port") serverCmd.Flags().String("pprofPort", defaultDFSPprofPort, "pprof port") + serverCmd.Flags().Int("feedCacheSize", -1, "Keep feed updates in lru cache for faster access. -1 to disable") + serverCmd.Flags().String("feedCacheTTL", "0s", "How long to keep feed updates in lru cache. 0s to disable") serverCmd.Flags().String("cookieDomain", defaultCookieDomain, "the domain to use in the cookie") serverCmd.Flags().String("postageBlockId", "", "the postage block used to store the data in bee") serverCmd.Flags().StringSlice("cors-origins", defaultCORSAllowedOrigins, "allow CORS headers for the given origins") @@ -334,6 +362,21 @@ func startHttpService(logger logging.Logger) *http.Server { podRouter.HandleFunc("/fork", handler.PodForkHandler).Methods("POST") podRouter.HandleFunc("/fork-from-reference", handler.PodForkFromReferenceHandler).Methods("POST") + groupRouter := baseRouter.PathPrefix("/group/").Subrouter() + groupRouter.Use(handler.LoginMiddleware) + groupRouter.HandleFunc("/new", handler.GroupCreateHandler).Methods("POST") + groupRouter.HandleFunc("/ls", handler.GroupListHandler).Methods("GET") + groupRouter.HandleFunc("/delete", handler.GroupDeleteHandler).Methods("DELETE") + groupRouter.HandleFunc("/delete-shared", handler.GroupDeleteSharedHandler).Methods("DELETE") + groupRouter.HandleFunc("/accept", handler.GroupAcceptInviteHandler).Methods("POST") + groupRouter.HandleFunc("/invite", handler.GroupAddMemberHandler).Methods("POST") + groupRouter.HandleFunc("/remove", handler.GroupRemoveMemberHandler).Methods("POST") + groupRouter.HandleFunc("/update-permission", handler.GroupUpdatePermissionHandler).Methods("POST") + groupRouter.HandleFunc("/close", handler.GroupCloseHandler).Methods("POST") + groupRouter.HandleFunc("/members", handler.GroupGetMembers).Methods("GET") + groupRouter.HandleFunc("/permission", handler.GroupGetPermission).Methods("GET") + groupRouter.HandleFunc("/open", handler.GroupOpenHandler).Methods("POST") + // directory related handlers dirRouter := baseRouter.PathPrefix("/dir/").Subrouter() dirRouter.Use(handler.LoginMiddleware) diff --git a/cmd/dfs/cmd/server_test.go b/cmd/dfs/cmd/server_test.go index 0b0f557d..00547bf4 100644 --- a/cmd/dfs/cmd/server_test.go +++ b/cmd/dfs/cmd/server_test.go @@ -17,8 +17,14 @@ import ( "testing" "time" + "github.com/fairdatasociety/fairOS-dfs/pkg/acl/acl" + "github.com/stretchr/testify/assert" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/cmd/common" "github.com/fairdatasociety/fairOS-dfs/pkg/api" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/dfs" mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/ensm/eth/mock" @@ -43,11 +49,18 @@ func randStringRunes(n int) string { } func TestApis(t *testing.T) { - mockClient := mock.NewMockBeeClient() + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) ens := mock2.NewMockNamespaceManager() - logger := logging.New(io.Discard, logrus.ErrorLevel) - users := user.NewUsers(mockClient, ens, logger) + users := user.NewUsers(mockClient, ens, 500, 0, logger) dfsApi := dfs.NewMockDfsAPI(mockClient, users, logger) handler = api.NewMockHandler(dfsApi, logger, []string{"http://localhost:3000"}) defer handler.Close() @@ -64,7 +77,7 @@ func TestApis(t *testing.T) { } }() - // wait 10 seconds for the server to start + // wait for the server to start <-time.After(time.Second * 3) t.Run("login-fail-test", func(t *testing.T) { c := http.Client{Timeout: time.Duration(1) * time.Minute} @@ -1071,6 +1084,267 @@ func TestApis(t *testing.T) { } }) + t.Run("group-test", func(t *testing.T) { + c := http.Client{Timeout: time.Duration(1) * time.Minute} + userRequest := &common.UserSignupRequest{ + UserName: randStringRunes(16), + Password: randStringRunes(12), + } + + userBytes, err := json.Marshal(userRequest) + if err != nil { + t.Fatal(err) + } + + signupRequestDataHttpReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", basev2, string(common.UserSignup)), bytes.NewBuffer(userBytes)) + if err != nil { + t.Fatal(err) + } + signupRequestDataHttpReq.Header.Add("Content-Type", "application/json") + signupRequestDataHttpReq.Header.Add("Content-Length", strconv.Itoa(len(userBytes))) + signupRequestResp, err := c.Do(signupRequestDataHttpReq) + if err != nil { + t.Fatal(err) + } + + err = signupRequestResp.Body.Close() + if err != nil { + t.Fatal(err) + } + if signupRequestResp.StatusCode != http.StatusCreated { + t.Fatal("Signup failed", signupRequestResp.StatusCode) + } + + userLoginHttpReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", basev2, string(common.UserLogin)), bytes.NewBuffer(userBytes)) + if err != nil { + t.Fatal(err) + + } + userLoginHttpReq.Header.Add("Content-Type", "application/json") + userLoginHttpReq.Header.Add("Content-Length", strconv.Itoa(len(userBytes))) + userLoginResp, err := c.Do(userLoginHttpReq) + if err != nil { + t.Fatal(err) + } + err = userLoginResp.Body.Close() + if err != nil { + t.Fatal(err) + } + if userLoginResp.StatusCode != http.StatusOK { + t.Fatal("user should be able to login") + } + cookie := userLoginResp.Header["Set-Cookie"] + groupRequest := &api.GroupNameRequest{ + GroupName: randStringRunes(16), + } + + groupBytes, err := json.Marshal(groupRequest) + if err != nil { + t.Fatal(err) + } + + groupNewHttpReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", basev1, "/group/new"), bytes.NewBuffer(groupBytes)) + if err != nil { + t.Fatal(err) + } + groupNewHttpReq.Header.Set("Cookie", cookie[0]) + groupNewHttpReq.Header.Add("Content-Type", "application/json") + groupNewHttpReq.Header.Add("Content-Length", strconv.Itoa(len(groupBytes))) + groupNewResp, err := c.Do(groupNewHttpReq) + if err != nil { + t.Fatal(err) + } + + err = groupNewResp.Body.Close() + if err != nil { + t.Fatal(err) + } + if groupNewResp.StatusCode != 201 { + t.Fatal("group creation failed") + } + + // check for own permission + groupPermHttpReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s%s?groupName=%s", basev1, "/group/permission", groupRequest.GroupName), nil) + if err != nil { + t.Fatal(err) + } + groupPermHttpReq.Header.Set("Cookie", cookie[0]) + groupPermHttpReq.Header.Add("Content-Type", "application/json") + groupPermHttpReq.Header.Add("Content-Length", strconv.Itoa(len(groupBytes))) + groupPermResp, err := c.Do(groupPermHttpReq) + if err != nil { + t.Fatal(err) + } + + permResp, err := io.ReadAll(groupPermResp.Body) + if err != nil { + t.Fatal(err) + } + perm := &api.GroupPermissionResponse{} + err = json.Unmarshal(permResp, &perm) + if err != nil { + t.Fatal(err) + } + err = groupPermResp.Body.Close() + if err != nil { + t.Fatal(err) + } + if groupPermResp.StatusCode != 200 { + t.Fatal("group permission failed") + } + + if !assert.Equal(t, perm.Permission, acl.PermissionWrite) { + t.Fatal("permission should be write") + } + + entries := []struct { + path string + isDir bool + size int64 + content []byte + }{ + { + path: "/dir1", + isDir: true, + }, + { + path: "/dir2", + isDir: true, + }, + { + path: "/dir3", + isDir: true, + }, + { + path: "/file1", + size: 1024 * 1024, + }, + { + path: "/dir1/file11", + size: 1024 * 512, + }, + { + path: "/dir1/file12", + size: 1024 * 1024, + }, + { + path: "/dir3/file31", + size: 1024 * 1024, + }, + { + path: "/dir3/file32", + size: 1024 * 1024, + }, + { + path: "/dir3/file33", + size: 1024, + }, + { + path: "/dir2/dir4", + isDir: true, + }, + { + path: "/dir2/dir4/dir5", + isDir: true, + }, + { + path: "/dir2/dir4/file241", + size: 5 * 1024 * 1024, + }, + { + path: "/dir2/dir4/dir5/file2451", + size: 10 * 1024 * 1024, + }, + } + + for _, v := range entries { + if v.isDir { + mkdirRqst := common.FileSystemRequest{ + GroupName: groupRequest.GroupName, + DirectoryPath: v.path, + } + mkDirBytes, err := json.Marshal(mkdirRqst) + if err != nil { + t.Fatal(err) + } + mkDirHttpReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", basev1, string(common.DirMkdir)), bytes.NewBuffer(mkDirBytes)) + if err != nil { + t.Fatal(err) + + } + mkDirHttpReq.Header.Set("Cookie", cookie[0]) + mkDirHttpReq.Header.Add("Content-Type", "application/json") + mkDirHttpReq.Header.Add("Content-Length", strconv.Itoa(len(mkDirBytes))) + mkDirResp, err := c.Do(mkDirHttpReq) + if err != nil { + t.Fatal(err) + } + err = mkDirResp.Body.Close() + if err != nil { + t.Fatal(err) + } + if mkDirResp.StatusCode != 201 { + t.Fatal("mkdir failed") + } + } else { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + contentLength := fmt.Sprintf("%d", v.size) + + err = writer.WriteField("groupName", groupRequest.GroupName) + if err != nil { + t.Fatal(err) + } + err = writer.WriteField("contentLength", contentLength) + if err != nil { + t.Fatal(err) + } + err = writer.WriteField("dirPath", filepath.Dir(v.path)) + if err != nil { + t.Fatal(err) + } + err = writer.WriteField("blockSize", "1Mb") + if err != nil { + t.Fatal(err) + } + part, err := writer.CreateFormFile("files", filepath.Base(v.path)) + if err != nil { + t.Fatal(err) + } + reader := &io.LimitedReader{R: rand.Reader, N: v.size} + _, err = io.Copy(part, reader) + if err != nil { + t.Fatal(err) + } + + err = writer.Close() + if err != nil { + t.Fatal(err) + } + + uploadReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", basev1, string(common.FileUpload)), body) + if err != nil { + t.Fatal(err) + + } + uploadReq.Header.Set("Cookie", cookie[0]) + contentType := fmt.Sprintf("multipart/form-data;boundary=%v", writer.Boundary()) + uploadReq.Header.Add("Content-Type", contentType) + uploadResp, err := c.Do(uploadReq) + if err != nil { + t.Fatal(err) + } + err = uploadResp.Body.Close() + if err != nil { + t.Fatal(err) + } + if uploadResp.StatusCode != 200 { + t.Fatal("upload failed") + } + } + } + }) + t.Run("ws test", func(t *testing.T) { u := url.URL{Scheme: "ws", Host: base, Path: "/ws/v1/"} header := http.Header{} @@ -1167,7 +1441,7 @@ func TestApis(t *testing.T) { } // userLogin - podName := fmt.Sprintf("%d", time.Now().UnixNano()) + //podName := fmt.Sprintf("%d", time.Now().UnixNano()) login := &common.WebsocketRequest{ Event: common.UserLogin, @@ -1214,521 +1488,521 @@ func TestApis(t *testing.T) { if err != nil { t.Fatal(err) } - - // userStat - userStat := &common.WebsocketRequest{ - Event: common.UserStat, - } - data, err = json.Marshal(userStat) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // podNew - podNew := &common.WebsocketRequest{ - Event: common.PodNew, - Params: common.PodRequest{ - PodName: podName, - Password: userRequest.Password, - }, - } - data, err = json.Marshal(podNew) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // podLs - podLs := &common.WebsocketRequest{ - Event: common.PodLs, - } - data, err = json.Marshal(podLs) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // mkdir - mkDir := &common.WebsocketRequest{ - Event: common.DirMkdir, - Params: common.FileRequest{ - PodName: podName, - DirPath: "/d", - }, - } - data, err = json.Marshal(mkDir) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // rmDir - rmDir := &common.WebsocketRequest{ - Event: common.DirRmdir, - Params: common.FileRequest{ - PodName: podName, - DirPath: "/d", - }, - } - data, err = json.Marshal(rmDir) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // dirLs - dirLs := &common.WebsocketRequest{ - Event: common.DirLs, - Params: common.FileRequest{ - PodName: podName, - DirPath: "/", - }, - } - data, err = json.Marshal(dirLs) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // dirStat - dirStat := &common.WebsocketRequest{ - Event: common.DirStat, - Params: common.FileRequest{ - PodName: podName, - DirPath: "/", - }, - } - data, err = json.Marshal(dirStat) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // dirPresent - dirPresent := &common.WebsocketRequest{ - Event: common.DirIsPresent, - Params: common.FileRequest{ - PodName: podName, - DirPath: "/d", - }, - } - data, err = json.Marshal(dirPresent) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // Upload - upload := &common.WebsocketRequest{ - Event: common.FileUpload, - Params: common.FileRequest{ - PodName: podName, - DirPath: "/", - BlockSize: "1Mb", - FileName: "README.md", - }, - } - data, err = json.Marshal(upload) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - file, err := os.Open("../../../README.md") - if err != nil { - panic(err) - } - // skipcq: GO-S2307 - defer file.Close() - body := &bytes.Buffer{} - _, err = io.Copy(body, file) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.BinaryMessage, body.Bytes()) - if err != nil { - t.Fatal(err) - } - - // Download - download := &common.WebsocketRequest{ - Event: common.FileDownload, - Params: common.FileDownloadRequest{ - PodName: podName, - Filepath: "/README.md", - }, - } - data, err = json.Marshal(download) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // stat - stat := &common.WebsocketRequest{ - Event: common.FileStat, - Params: common.FileSystemRequest{ - PodName: podName, - DirectoryPath: "/README.md", - }, - } - data, err = json.Marshal(stat) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - table := "kv_1" - // kvCreate - kvCreate := &common.WebsocketRequest{ - Event: common.KVCreate, - Params: common.KVRequest{ - PodName: podName, - TableName: table, - IndexType: "string", - }, - } - data, err = json.Marshal(kvCreate) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // kvList - kvList := &common.WebsocketRequest{ - Event: common.KVList, - Params: common.KVRequest{ - PodName: podName, - }, - } - data, err = json.Marshal(kvList) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // kvOpen - kvOpen := &common.WebsocketRequest{ - Event: common.KVOpen, - Params: common.KVRequest{ - PodName: podName, - TableName: table, - }, - } - data, err = json.Marshal(kvOpen) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // kvEntryPut - kvEntryPut := &common.WebsocketRequest{ - Event: common.KVEntryPut, - Params: common.KVRequest{ - PodName: podName, - TableName: table, - Key: "key1", - Value: "value1", - }, - } - data, err = json.Marshal(kvEntryPut) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // kvCount - kvCount := &common.WebsocketRequest{ - Event: common.KVCount, - Params: common.KVRequest{ - PodName: podName, - TableName: table, - }, - } - data, err = json.Marshal(kvCount) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // kvGet - kvGet := &common.WebsocketRequest{ - Event: common.KVEntryGet, - Params: common.KVRequest{ - PodName: podName, - TableName: table, - Key: "key1", - }, - } - data, err = json.Marshal(kvGet) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // kvSeek - kvSeek := &common.WebsocketRequest{ - Event: common.KVSeek, - Params: common.KVRequest{ - PodName: podName, - TableName: table, - StartPrefix: "key", - }, - } - data, err = json.Marshal(kvSeek) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // kvSeek - kvSeekNext := &common.WebsocketRequest{ - Event: common.KVSeekNext, - Params: common.KVRequest{ - PodName: podName, - TableName: table, - }, - } - data, err = json.Marshal(kvSeekNext) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // kvEntryDel - kvEntryDel := &common.WebsocketRequest{ - Event: common.KVEntryDelete, - Params: common.KVRequest{ - PodName: podName, - TableName: table, - Key: "key1", - }, - } - data, err = json.Marshal(kvEntryDel) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - docTable := "doc_1" - // docCreate - docCreate := &common.WebsocketRequest{ - Event: common.DocCreate, - Params: common.DocRequest{ - PodName: podName, - TableName: docTable, - SimpleIndex: "first_name=string,age=number", - Mutable: true, - }, - } - data, err = json.Marshal(docCreate) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // docLs - docLs := &common.WebsocketRequest{ - Event: common.DocList, - Params: common.DocRequest{ - PodName: podName, - TableName: docTable, - }, - } - data, err = json.Marshal(docLs) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // docOpen - docOpen := &common.WebsocketRequest{ - Event: common.DocOpen, - Params: common.DocRequest{ - PodName: podName, - TableName: docTable, - }, - } - data, err = json.Marshal(docOpen) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // docEntryPut - docEntryPut := &common.WebsocketRequest{ - Event: common.DocEntryPut, - Params: common.DocRequest{ - PodName: podName, - TableName: docTable, - Document: `{"id":"1", "first_name": "Hello1", "age": 11}`, - }, - } - data, err = json.Marshal(docEntryPut) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // docEntryGet - docEntryGet := &common.WebsocketRequest{ - Event: common.DocEntryGet, - Params: common.DocRequest{ - PodName: podName, - TableName: docTable, - ID: "1", - }, - } - data, err = json.Marshal(docEntryGet) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // docFind - docFind := &common.WebsocketRequest{ - Event: common.DocFind, - Params: common.DocRequest{ - PodName: podName, - TableName: docTable, - Expression: `age>10`, - }, - } - data, err = json.Marshal(docFind) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // docCount - docCount := &common.WebsocketRequest{ - Event: common.DocCount, - Params: common.DocRequest{ - PodName: podName, - TableName: docTable, - }, - } - data, err = json.Marshal(docCount) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // docEntryGet - docEntryDel := &common.WebsocketRequest{ - Event: common.DocEntryDel, - Params: common.DocRequest{ - PodName: podName, - TableName: docTable, - ID: "1", - }, - } - data, err = json.Marshal(docEntryDel) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } - - // docDel - docDel := &common.WebsocketRequest{ - Event: common.DocDelete, - Params: common.DocRequest{ - PodName: podName, - TableName: docTable, - }, - } - data, err = json.Marshal(docDel) - if err != nil { - t.Fatal(err) - } - err = c.WriteMessage(websocket.TextMessage, data) - if err != nil { - t.Fatal(err) - } + // + //// userStat + //userStat := &common.WebsocketRequest{ + // Event: common.UserStat, + //} + //data, err = json.Marshal(userStat) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// podNew + //podNew := &common.WebsocketRequest{ + // Event: common.PodNew, + // Params: common.PodRequest{ + // PodName: podName, + // Password: userRequest.Password, + // }, + //} + //data, err = json.Marshal(podNew) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// podLs + //podLs := &common.WebsocketRequest{ + // Event: common.PodLs, + //} + //data, err = json.Marshal(podLs) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// mkdir + //mkDir := &common.WebsocketRequest{ + // Event: common.DirMkdir, + // Params: common.FileRequest{ + // PodName: podName, + // DirPath: "/d", + // }, + //} + //data, err = json.Marshal(mkDir) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// rmDir + //rmDir := &common.WebsocketRequest{ + // Event: common.DirRmdir, + // Params: common.FileRequest{ + // PodName: podName, + // DirPath: "/d", + // }, + //} + //data, err = json.Marshal(rmDir) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// dirLs + //dirLs := &common.WebsocketRequest{ + // Event: common.DirLs, + // Params: common.FileRequest{ + // PodName: podName, + // DirPath: "/", + // }, + //} + //data, err = json.Marshal(dirLs) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// dirStat + //dirStat := &common.WebsocketRequest{ + // Event: common.DirStat, + // Params: common.FileRequest{ + // PodName: podName, + // DirPath: "/", + // }, + //} + //data, err = json.Marshal(dirStat) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// dirPresent + //dirPresent := &common.WebsocketRequest{ + // Event: common.DirIsPresent, + // Params: common.FileRequest{ + // PodName: podName, + // DirPath: "/d", + // }, + //} + //data, err = json.Marshal(dirPresent) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// Upload + //upload := &common.WebsocketRequest{ + // Event: common.FileUpload, + // Params: common.FileRequest{ + // PodName: podName, + // DirPath: "/", + // BlockSize: "1Mb", + // FileName: "README.md", + // }, + //} + //data, err = json.Marshal(upload) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + //file, err := os.Open("../../../README.md") + //if err != nil { + // panic(err) + //} + //// skipcq: GO-S2307 + //defer file.Close() + //body := &bytes.Buffer{} + //_, err = io.Copy(body, file) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.BinaryMessage, body.Bytes()) + //if err != nil { + // t.Fatal(err) + //} + // + //// Download + //download := &common.WebsocketRequest{ + // Event: common.FileDownload, + // Params: common.FileDownloadRequest{ + // PodName: podName, + // Filepath: "/README.md", + // }, + //} + //data, err = json.Marshal(download) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// stat + //stat := &common.WebsocketRequest{ + // Event: common.FileStat, + // Params: common.FileSystemRequest{ + // PodName: podName, + // DirectoryPath: "/README.md", + // }, + //} + //data, err = json.Marshal(stat) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //table := "kv_1" + //// kvCreate + //kvCreate := &common.WebsocketRequest{ + // Event: common.KVCreate, + // Params: common.KVRequest{ + // PodName: podName, + // TableName: table, + // IndexType: "string", + // }, + //} + //data, err = json.Marshal(kvCreate) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// kvList + //kvList := &common.WebsocketRequest{ + // Event: common.KVList, + // Params: common.KVRequest{ + // PodName: podName, + // }, + //} + //data, err = json.Marshal(kvList) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// kvOpen + //kvOpen := &common.WebsocketRequest{ + // Event: common.KVOpen, + // Params: common.KVRequest{ + // PodName: podName, + // TableName: table, + // }, + //} + //data, err = json.Marshal(kvOpen) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// kvEntryPut + //kvEntryPut := &common.WebsocketRequest{ + // Event: common.KVEntryPut, + // Params: common.KVRequest{ + // PodName: podName, + // TableName: table, + // Key: "key1", + // Value: "value1", + // }, + //} + //data, err = json.Marshal(kvEntryPut) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// kvCount + //kvCount := &common.WebsocketRequest{ + // Event: common.KVCount, + // Params: common.KVRequest{ + // PodName: podName, + // TableName: table, + // }, + //} + //data, err = json.Marshal(kvCount) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// kvGet + //kvGet := &common.WebsocketRequest{ + // Event: common.KVEntryGet, + // Params: common.KVRequest{ + // PodName: podName, + // TableName: table, + // Key: "key1", + // }, + //} + //data, err = json.Marshal(kvGet) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// kvSeek + //kvSeek := &common.WebsocketRequest{ + // Event: common.KVSeek, + // Params: common.KVRequest{ + // PodName: podName, + // TableName: table, + // StartPrefix: "key", + // }, + //} + //data, err = json.Marshal(kvSeek) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// kvSeek + //kvSeekNext := &common.WebsocketRequest{ + // Event: common.KVSeekNext, + // Params: common.KVRequest{ + // PodName: podName, + // TableName: table, + // }, + //} + //data, err = json.Marshal(kvSeekNext) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// kvEntryDel + //kvEntryDel := &common.WebsocketRequest{ + // Event: common.KVEntryDelete, + // Params: common.KVRequest{ + // PodName: podName, + // TableName: table, + // Key: "key1", + // }, + //} + //data, err = json.Marshal(kvEntryDel) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //docTable := "doc_1" + //// docCreate + //docCreate := &common.WebsocketRequest{ + // Event: common.DocCreate, + // Params: common.DocRequest{ + // PodName: podName, + // TableName: docTable, + // SimpleIndex: "first_name=string,age=number", + // Mutable: true, + // }, + //} + //data, err = json.Marshal(docCreate) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// docLs + //docLs := &common.WebsocketRequest{ + // Event: common.DocList, + // Params: common.DocRequest{ + // PodName: podName, + // TableName: docTable, + // }, + //} + //data, err = json.Marshal(docLs) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// docOpen + //docOpen := &common.WebsocketRequest{ + // Event: common.DocOpen, + // Params: common.DocRequest{ + // PodName: podName, + // TableName: docTable, + // }, + //} + //data, err = json.Marshal(docOpen) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// docEntryPut + //docEntryPut := &common.WebsocketRequest{ + // Event: common.DocEntryPut, + // Params: common.DocRequest{ + // PodName: podName, + // TableName: docTable, + // Document: `{"id":"1", "first_name": "Hello1", "age": 11}`, + // }, + //} + //data, err = json.Marshal(docEntryPut) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// docEntryGet + //docEntryGet := &common.WebsocketRequest{ + // Event: common.DocEntryGet, + // Params: common.DocRequest{ + // PodName: podName, + // TableName: docTable, + // ID: "1", + // }, + //} + //data, err = json.Marshal(docEntryGet) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// docFind + //docFind := &common.WebsocketRequest{ + // Event: common.DocFind, + // Params: common.DocRequest{ + // PodName: podName, + // TableName: docTable, + // Expression: `age>10`, + // }, + //} + //data, err = json.Marshal(docFind) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// docCount + //docCount := &common.WebsocketRequest{ + // Event: common.DocCount, + // Params: common.DocRequest{ + // PodName: podName, + // TableName: docTable, + // }, + //} + //data, err = json.Marshal(docCount) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// docEntryGet + //docEntryDel := &common.WebsocketRequest{ + // Event: common.DocEntryDel, + // Params: common.DocRequest{ + // PodName: podName, + // TableName: docTable, + // ID: "1", + // }, + //} + //data, err = json.Marshal(docEntryDel) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} + // + //// docDel + //docDel := &common.WebsocketRequest{ + // Event: common.DocDelete, + // Params: common.DocRequest{ + // PodName: podName, + // TableName: docTable, + // }, + //} + //data, err = json.Marshal(docDel) + //if err != nil { + // t.Fatal(err) + //} + //err = c.WriteMessage(websocket.TextMessage, data) + //if err != nil { + // t.Fatal(err) + //} // user Logout uLogout := &common.WebsocketRequest{ Event: common.UserLogout, diff --git a/go.mod b/go.mod index 095cfe46..2c8b4f65 100644 --- a/go.mod +++ b/go.mod @@ -1,118 +1,160 @@ module github.com/fairdatasociety/fairOS-dfs -go 1.20 +go 1.21 require ( github.com/btcsuite/btcd v0.22.3 github.com/c-bata/go-prompt v0.2.6 github.com/dustin/go-humanize v1.0.1 - github.com/ethereum/go-ethereum v1.12.2 - github.com/ethersphere/bee v1.16.1 + github.com/ethereum/go-ethereum v1.13.12 + github.com/ethersphere/bee v1.18.2 github.com/ethersphere/bmt v0.1.4 github.com/fairdatasociety/fairOS-dfs-utils v0.0.0-20221230123929-aec4ed8b854d - github.com/golang-jwt/jwt/v5 v5.0.0 + github.com/golang-jwt/jwt/v5 v5.2.0 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/gorilla/mux v1.8.0 - github.com/gorilla/securecookie v1.1.1 - github.com/gorilla/websocket v1.5.0 - github.com/hashicorp/golang-lru v1.0.2 + github.com/gorilla/mux v1.8.1 + github.com/gorilla/securecookie v1.1.2 + github.com/gorilla/websocket v1.5.1 + github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/klauspost/pgzip v1.2.6 - github.com/miguelmota/go-ethereum-hdwallet v0.1.1 + github.com/miguelmota/go-ethereum-hdwallet v0.1.2 github.com/mitchellh/go-homedir v1.1.0 github.com/plexsysio/taskmanager v0.0.0-20211220123746-de5ebdd49ae2 - github.com/rs/cors v1.9.0 + github.com/rs/cors v1.10.1 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.7.0 - github.com/spf13/viper v1.16.0 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.8.4 github.com/swaggo/http-swagger v1.3.4 - github.com/swaggo/swag v1.16.1 + github.com/swaggo/swag v1.16.3 github.com/tinygrasshopper/bettercsv v0.0.1 github.com/tyler-smith/go-bip39 v1.1.0 - github.com/wealdtech/go-ens/v3 v3.5.5 - go.uber.org/goleak v1.2.1 - golang.org/x/crypto v0.12.0 - golang.org/x/term v0.11.0 + github.com/wealdtech/go-ens/v3 v3.6.0 + go.uber.org/goleak v1.3.0 + golang.org/x/crypto v0.19.0 + golang.org/x/term v0.17.0 gopkg.in/yaml.v2 v2.4.0 resenje.org/jsonhttp v0.2.3 ) require ( + github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/KyleBanks/depth v1.2.1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/armon/go-radix v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect + github.com/casbin/casbin/v2 v2.35.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/ethersphere/go-price-oracle-abi v0.1.0 // indirect + github.com/ethersphere/go-storage-incentives-abi v0.6.0 // indirect + github.com/ethersphere/go-sw3-abi v0.4.0 // indirect + github.com/ethersphere/langos v1.0.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/spec v0.20.7 // indirect github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-stack/stack v1.8.1 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/gorilla/handlers v1.5.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/holiman/uint256 v1.2.3 // indirect + github.com/holiman/uint256 v1.2.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/ipfs/go-cid v0.3.2 // indirect + github.com/ipfs/go-cid v0.4.1 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.15.15 // indirect - github.com/klauspost/cpuid/v2 v2.2.1 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/libp2p/go-libp2p v0.30.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-tty v0.0.3 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect + github.com/miekg/dns v1.1.55 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multibase v0.1.1 // indirect - github.com/multiformats/go-multihash v0.2.1 // indirect + github.com/multiformats/go-multiaddr v0.11.0 // indirect + github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multicodec v0.9.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect + github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/term v1.2.0-beta.2 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.39.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.9.5 // indirect - github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/supranational/blst v0.3.11 // indirect github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect - github.com/tklauser/go-sysconf v0.3.9 // indirect - github.com/tklauser/numcpus v0.4.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/uber/jaeger-client-go v2.24.0+incompatible // indirect + github.com/uber/jaeger-lib v2.2.0+incompatible // indirect + github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/wealdtech/go-multicodec v1.4.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - go.uber.org/atomic v1.10.0 // indirect - golang.org/x/exp v0.0.0-20230810033253-352e893a4cad // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect - golang.org/x/tools v0.9.1 // indirect - google.golang.org/protobuf v1.30.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.15.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/blake3 v1.1.7 // indirect + lukechampine.com/blake3 v1.2.1 // indirect + resenje.org/multex v0.1.0 // indirect + resenje.org/singleflight v0.4.0 // indirect + resenje.org/web v0.9.2 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) replace github.com/codahale/hdrhistogram => github.com/HdrHistogram/hdrhistogram-go v0.0.0-20200919145931-8dac23c8dac1 diff --git a/go.sum b/go.sum index 7bb21f29..73bf230b 100644 --- a/go.sum +++ b/go.sum @@ -1,94 +1,23 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= -github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= -github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/HdrHistogram/hdrhistogram-go v0.0.0-20200919145931-8dac23c8dac1 h1:nEjGZtKHMK92888VT6XkzKwyiW14v5FFRGeWq2uV7N0= +github.com/HdrHistogram/hdrhistogram-go v0.0.0-20200919145931-8dac23c8dac1/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= -github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= -github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= -github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= -github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= -github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= -github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btcd v0.22.3 h1:kYNaWFvOw6xvqP0vR20RP1Zq1DVMBxEO8QN5d1/EfNg= github.com/btcsuite/btcd v0.22.3/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= @@ -97,108 +26,93 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOF github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/casbin/casbin/v2 v2.35.0 h1:f0prVg9LgTJTihjAxWEZhfJptXvah1GpZh12sb5KXNA= +github.com/casbin/casbin/v2 v2.35.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= -github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= -github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= +github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= +github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= +github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= +github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= -github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= -github.com/consensys/gnark-crypto v0.10.0 h1:zRh22SR7o4K35SoNqouS9J/TKHTyU2QWaj5ldehyXtA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= -github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= -github.com/ethereum/go-ethereum v1.10.4/go.mod h1:nEE0TP5MtxGzOMd7egIrbPJMQBnhVU3ELNxhBglIzhg= -github.com/ethereum/go-ethereum v1.12.2 h1:eGHJ4ij7oyVqUQn48LBz3B7pvQ8sV0wGJiIE6gDq/6Y= -github.com/ethereum/go-ethereum v1.12.2/go.mod h1:1cRAEV+rp/xX0zraSCBnu9Py3HQ+geRMj3HdR+k0wfI= -github.com/ethersphere/bee v1.16.1 h1:eS3Y2fei1508SQnNPwPM5evtorjDdBlHoeynP7JSnGI= -github.com/ethersphere/bee v1.16.1/go.mod h1:KH/BT0hLu5fvvufQApHZesHD8mYepzou1zxKrmwgvtM= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.13.12 h1:iDr9UM2JWkngBHGovRJEQn4Kor7mT4gt9rUZqB5M29Y= +github.com/ethereum/go-ethereum v1.13.12/go.mod h1:hKL2Qcj1OvStXNSEDbucexqnEt1Wh4Cz329XsjAalZY= +github.com/ethersphere/bee v1.18.2 h1:bSngtJGDBYkB8HcPHMjKcoBiYNllqChuykpy1IVaGfA= +github.com/ethersphere/bee v1.18.2/go.mod h1:k5jZVd/o6WCz9JLACiJKccyR0efhftZ98Qbx5GYMb+k= github.com/ethersphere/bmt v0.1.4 h1:+rkWYNtMgDx6bkNqGdWu+U9DgGI1rRZplpSW3YhBr1Q= github.com/ethersphere/bmt v0.1.4/go.mod h1:Yd8ft1U69WDuHevZc/rwPxUv1rzPSMpMnS6xbU53aY8= +github.com/ethersphere/go-price-oracle-abi v0.1.0 h1:yg/hK8nETNvk+GEBASlbakMFv/CVp7HXiycrHw1pRV8= +github.com/ethersphere/go-price-oracle-abi v0.1.0/go.mod h1:sI/Qj4/zJ23/b1enzwMMv0/hLTpPNVNacEwCWjo6yBk= +github.com/ethersphere/go-storage-incentives-abi v0.6.0 h1:lfGViU/wJg/CyXlntNvTQpqQ2A4QYGLJ7jo+Pw+H+a4= +github.com/ethersphere/go-storage-incentives-abi v0.6.0/go.mod h1:SXvJVtM4sEsaSKD0jc1ClpDLw8ErPoROZDme4Wrc/Nc= +github.com/ethersphere/go-sw3-abi v0.4.0 h1:T3ANY+ktWrPAwe2U0tZi+DILpkHzto5ym/XwV/Bbz8g= +github.com/ethersphere/go-sw3-abi v0.4.0/go.mod h1:BmpsvJ8idQZdYEtWnvxA8POYQ8Rl/NhyCdF0zLMOOJU= +github.com/ethersphere/langos v1.0.0 h1:NBtNKzXTTRSue95uOlzPN4py7Aofs0xWPzyj4AI1Vcc= +github.com/ethersphere/langos v1.0.0/go.mod h1:dlcN2j4O8sQ+BlCaxeBu43bgr4RQ+inJ+pHwLeZg5Tw= github.com/fairdatasociety/fairOS-dfs-utils v0.0.0-20221230123929-aec4ed8b854d h1:4QgyFcv+J02YdPZ92oiCjjQ8QtGl/UbLTAypKb+WfZc= github.com/fairdatasociety/fairOS-dfs-utils v0.0.0-20221230123929-aec4ed8b854d/go.mod h1:f4lfxKoK1n4S+2II4lyVmuvWISAMThqH+uc/XL62tzU= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= +github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= -github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -210,185 +124,116 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= -github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= -github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= +github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= -github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= -github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= -github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= -github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= -github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= -github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= -github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= -github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc= -github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw= -github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= +github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= -github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= -github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI= -github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= -github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/libp2p/go-libp2p v0.30.0 h1:9EZwFtJPFBcs/yJTnP90TpN1hgrT/EsFfM+OZuwV87U= +github.com/libp2p/go-libp2p v0.30.0/go.mod h1:nr2g5V7lfftwgiJ78/HrID+pwvayLyqKCEirT2Y3Byg= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -396,632 +241,336 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miguelmota/go-ethereum-hdwallet v0.1.1 h1:zdXGlHao7idpCBjEGTXThVAtMKs+IxAgivZ75xqkWK0= -github.com/miguelmota/go-ethereum-hdwallet v0.1.1/go.mod h1:f9m9uXokAHA6WNoYOPjj4AqjJS5pquQRiYYj/XSyPYc= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miguelmota/go-ethereum-hdwallet v0.1.2 h1:mz9LO6V7QCRkLYb0AH17t5R8KeqCe3E+hx9YXpmZeXA= +github.com/miguelmota/go-ethereum-hdwallet v0.1.2/go.mod h1:fdNwFSoBFVBPnU0xpOd6l2ueqsPSH/Gch5kIvSvTGk8= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI= -github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= -github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108= -github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= +github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= +github.com/multiformats/go-multiaddr v0.11.0 h1:XqGyJ8ufbCE0HmTDwx2kPdsrQ36AGPZNZX6s6xfJH10= +github.com/multiformats/go-multiaddr v0.11.0/go.mod h1:gWUm0QLR4thQ6+ZF6SXUw8YjtwQSPapICM+NmCkxHSM= +github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= +github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= +github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= +github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= +github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= -github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/plexsysio/taskmanager v0.0.0-20211220123746-de5ebdd49ae2 h1:Y3ImPze8NO2iKaPFJ0LkqNFlkC8LjifxxxwEGxbkQ+A= github.com/plexsysio/taskmanager v0.0.0-20211220123746-de5ebdd49ae2/go.mod h1:mrUMc3N31sq3lEqDyCkbw0dBfOtNZyh+z8kHnNnpd7k= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= -github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= -github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY= github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= -github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg= -github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto= -github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tinygrasshopper/bettercsv v0.0.1 h1:N96aWjbUBN2q+KotgSI9FMR+1Y4IIBMVMPiL8qASK0k= github.com/tinygrasshopper/bettercsv v0.0.1/go.mod h1:0pXjg6Vm8+zAkvosNH2S0dx8gc7H1hDIV0pMzmq1vRI= -github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= -github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= -github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= -github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= -github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU= -github.com/wealdtech/go-ens/v3 v3.5.5 h1:/jq3CDItK0AsFnZtiFJK44JthkAMD5YE3WAJOh4i7lc= -github.com/wealdtech/go-ens/v3 v3.5.5/go.mod h1:w0EDKIm0dIQnqEKls6ORat/or+AVfPEdEXVfN71EeEE= +github.com/uber/jaeger-client-go v2.24.0+incompatible h1:CGchgJcHsDd2jWnaL4XngByMrXoGHh3n8oCqAKx0uMo= +github.com/uber/jaeger-client-go v2.24.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= +github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wealdtech/go-ens/v3 v3.6.0 h1:EAByZlHRQ3vxqzzwNi0GvEq1AjVozfWO4DMldHcoVg8= +github.com/wealdtech/go-ens/v3 v3.6.0/go.mod h1:hcmMr9qPoEgVSEXU2Bwzrn/9NczTWZ1rE53jIlqUpzw= github.com/wealdtech/go-multicodec v1.4.0 h1:iq5PgxwssxnXGGPTIK1srvt6U5bJwIp7k6kBrudIWxg= github.com/wealdtech/go-multicodec v1.4.0/go.mod h1:aedGMaTeYkIqi/KCPre1ho5rTb3hGpu/snBOS3GQLw4= -github.com/wealdtech/go-string2eth v1.1.0 h1:USJQmysUrBYYmZs7d45pMb90hRSyEwizP7lZaOZLDAw= -github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/wealdtech/go-string2eth v1.2.1 h1:u9sofvGFkp+uvTg4Nvsvy5xBaiw8AibGLLngfC4F76g= +github.com/wealdtech/go-string2eth v1.2.1/go.mod h1:9uwxm18zKZfrReXrGIbdiRYJtbE91iGcj6TezKKEx80= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= gitlab.com/nolash/go-mockbytes v0.0.7 h1:9XVFpEfY67kGBVJve3uV19kzqORdlo7V+q09OE6Yo54= gitlab.com/nolash/go-mockbytes v0.0.7/go.mod h1:KKOpNTT39j2Eo+P6uUTOncntfeKY6AFh/2CxuD5MpgE= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU= -golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= -lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= resenje.org/jsonhttp v0.2.3 h1:YEsBt1kxg3SB0+gDNmcxd9aredgtBlA70I0v+QsPysc= resenje.org/jsonhttp v0.2.3/go.mod h1:EDyeguyTWj2fU3D3SCE0qNTgthzyEkHYLM1uu0uikHU= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +resenje.org/multex v0.1.0 h1:am9Ndt8dIAeGVaztD8ClsSX+e0EP3mj6UdsvjukKZig= +resenje.org/multex v0.1.0/go.mod h1:3rHOoMrzqLNzgGWPcl/1GfzN52g7iaPXhbvTQ8TjGaM= +resenje.org/singleflight v0.4.0 h1:NdOEhCxEikK2S2WxGjZV9EGSsItolQKslOOi6pE1tJc= +resenje.org/singleflight v0.4.0/go.mod h1:lAgQK7VfjG6/pgredbQfmV0RvG/uVhKo6vSuZ0vCWfk= +resenje.org/web v0.9.2 h1:woq1MMoOr9WbsX6XU7JgWFJw2IOb2F06jlTqI9MWex0= +resenje.org/web v0.9.2/go.mod h1:YtrYB6u+gwTcTuIr2lBzsU6TVlUlbXNmkTB7rQz0O5Y= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/gomobile/dfs.go b/gomobile/dfs.go index 873bdf51..5b16387b 100644 --- a/gomobile/dfs.go +++ b/gomobile/dfs.go @@ -58,13 +58,16 @@ func Connect(beeEndpoint, postageBlockId, network, rpc string, logLevel int) err return fmt.Errorf("unknown network") } ensConfig.ProviderBackend = rpc + opts := &dfs.Options{ + Stamp: postageBlockId, + BeeApiEndpoint: beeEndpoint, + EnsConfig: ensConfig, + SubscriptionConfig: nil, + Logger: logger, + } api, err = dfs.NewDfsAPI( context.TODO(), - beeEndpoint, - postageBlockId, - ensConfig, - nil, - logger, + opts, ) return err } @@ -202,7 +205,7 @@ func PodReceiveInfo(podSharingReference string) (string, error) { } func DirPresent(podName, dirPath string) (string, error) { - present, err := api.IsDirPresent(podName, dirPath, sessionId) + present, err := api.IsDirPresent(podName, dirPath, sessionId, false) if err != nil { return "", err } @@ -213,7 +216,7 @@ func DirPresent(podName, dirPath string) (string, error) { } func DirMake(podName, dirPath string) (string, error) { - err := api.Mkdir(podName, dirPath, sessionId, 0) + err := api.Mkdir(podName, dirPath, sessionId, 0, false) if err != nil { return "", err } @@ -221,7 +224,7 @@ func DirMake(podName, dirPath string) (string, error) { } func DirRemove(podName, dirPath string) (string, error) { - err := api.RmDir(podName, dirPath, sessionId) + err := api.RmDir(podName, dirPath, sessionId, false) if err != nil { return "", err } @@ -229,7 +232,7 @@ func DirRemove(podName, dirPath string) (string, error) { } func DirList(podName, dirPath string) (string, error) { - dirs, files, err := api.ListDir(podName, dirPath, sessionId) + dirs, files, err := api.ListDir(podName, dirPath, sessionId, false) if err != nil { return "", err } @@ -249,7 +252,7 @@ func DirList(podName, dirPath string) (string, error) { } func DirStat(podName, dirPath string) (string, error) { - stat, err := api.DirectoryStat(podName, dirPath, sessionId) + stat, err := api.DirectoryStat(podName, dirPath, sessionId, false) if err != nil { return "", err } @@ -258,7 +261,7 @@ func DirStat(podName, dirPath string) (string, error) { } func FileShare(podName, dirPath, destinationUser string) (string, error) { - ref, err := api.ShareFile(podName, dirPath, destinationUser, sessionId) + ref, err := api.ShareFile(podName, dirPath, destinationUser, sessionId, false) if err != nil { return "", err } @@ -289,11 +292,11 @@ func FileReceiveInfo(podName, fileSharingReference string) (string, error) { } func FileDelete(podName, filePath string) error { - return api.DeleteFile(podName, filePath, sessionId) + return api.DeleteFile(podName, filePath, sessionId, false) } func FileStat(podName, filePath string) (string, error) { - stat, err := api.FileStat(podName, filePath, sessionId) + stat, err := api.FileStat(podName, filePath, sessionId, false) if err != nil { return "", err } @@ -316,16 +319,16 @@ func FileUpload(podName, filePath, dirPath, compression, blockSize string, overw if err != nil { return err } - return api.UploadFile(podName, fileInfo.Name(), sessionId, fileInfo.Size(), f, dirPath, compression, uint32(bs), 0, overwrite) + return api.UploadFile(podName, fileInfo.Name(), sessionId, fileInfo.Size(), f, dirPath, compression, uint32(bs), 0, overwrite, false) } func BlobUpload(data []byte, podName, fileName, dirPath, compression string, size, blockSize int64, overwrite bool) error { r := bytes.NewReader(data) - return api.UploadFile(podName, fileName, sessionId, size, r, dirPath, compression, uint32(blockSize), 0, overwrite) + return api.UploadFile(podName, fileName, sessionId, size, r, dirPath, compression, uint32(blockSize), 0, overwrite, false) } func FileDownload(podName, filePath string) ([]byte, error) { - r, _, err := api.DownloadFile(podName, filePath, sessionId) + r, _, err := api.DownloadFile(podName, filePath, sessionId, false) if err != nil { return nil, err } diff --git a/pkg/acl/acl.go b/pkg/acl/acl.go new file mode 100644 index 00000000..b990f759 --- /dev/null +++ b/pkg/acl/acl.go @@ -0,0 +1,11 @@ +package acl + +type ACL interface { + CreateGroup(groupName, ownerAddress string) error + AddMember(groupName, ownerAddress, memberAddress string, permission uint8) error + RemoveMember(groupName, ownerAddress, memberAddress string) error + RemoveGroup(groupName, ownerAddress string) error + GetGroupMembers(groupName, ownerAddress string) (map[string]uint8, error) + UpdatePermission(groupName, ownerAddress, memberAddress string, permission uint8) error + GetPermission(groupName, ownerAddress, memberAddress string) (uint8, error) +} diff --git a/pkg/acl/acl/controller.go b/pkg/acl/acl/controller.go new file mode 100644 index 00000000..27dedaf3 --- /dev/null +++ b/pkg/acl/acl/controller.go @@ -0,0 +1,141 @@ +package acl + +import ( + "bytes" + "encoding/json" + "io" + "sync" + + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore" + "github.com/fairdatasociety/fairOS-dfs/pkg/feed" + f "github.com/fairdatasociety/fairOS-dfs/pkg/file" + "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" +) + +const ( + NoPermission uint8 = 0 + PermissionRead uint8 = 1 + PermissionWrite uint8 = 2 + PermissionExecute uint8 = 4 +) + +type ACL struct { + c blockstore.Client + logger logging.Logger + f *feed.API + lock sync.Mutex + listMap map[string]map[string]uint8 +} + +func (a *ACL) GetGroupMembers(groupName, ownerAddress string) (map[string]uint8, error) { + err := a.loadPermissions(groupName, ownerAddress) + if err != nil { + return nil, err + } + a.lock.Lock() + defer a.lock.Unlock() + return a.listMap[groupName], nil +} + +func (a *ACL) UpdatePermission(groupName, ownerAddress, memberAddress string, permission uint8) error { + a.lock.Lock() + a.listMap[groupName][memberAddress] = permission + a.lock.Unlock() + + return a.storePermissions(groupName, ownerAddress) +} + +func (a *ACL) GetPermission(groupName, ownerAddress, memberAddress string) (uint8, error) { + err := a.loadPermissions(groupName, ownerAddress) + if err != nil { + return 0, err + } + a.lock.Lock() + defer a.lock.Unlock() + + return a.listMap[groupName][memberAddress], nil +} + +func (a *ACL) CreateGroup(groupName, ownerAddress string) error { + return a.storePermissions(groupName, ownerAddress) +} + +func (a *ACL) AddMember(groupName, ownerAddress, memberAddress string, permission uint8) error { + a.lock.Lock() + a.listMap[groupName][memberAddress] = permission + a.lock.Unlock() + return a.storePermissions(groupName, ownerAddress) +} + +func (a *ACL) RemoveMember(groupName, ownerAddress, memberAddress string) error { + a.lock.Lock() + delete(a.listMap[groupName], memberAddress) + a.lock.Unlock() + return a.storePermissions(groupName, ownerAddress) +} + +func (a *ACL) RemoveGroup(groupName, ownerAddress string) error { + a.lock.Lock() + delete(a.listMap, groupName) + a.lock.Unlock() + return a.storePermissions(groupName, ownerAddress) +} + +func NewACL(c blockstore.Client, f *feed.API, logger logging.Logger) *ACL { + return &ACL{ + c: c, + f: f, + logger: logger, + listMap: map[string]map[string]uint8{}, + } +} + +func (a *ACL) loadPermissions(group, ownerAddress string) error { + f2 := f.NewFile("", a.c, a.f, utils.HexToAddress(ownerAddress), nil, a.logger) + topicString := utils.CombinePathAndFile(ownerAddress, group) + r, _, err := f2.Download(topicString, "") + if err != nil { // skipcq: TCV-001 + return err + } + permissions := map[string]uint8{} + data, err := io.ReadAll(r) + if err != nil { // skipcq: TCV-001 + return err + } + + if len(data) == 0 { + a.listMap[group] = permissions + return nil + } + + err = json.Unmarshal(data, &permissions) + if err != nil { // skipcq: TCV-001 + return err + } + + a.lock.Lock() + defer a.lock.Unlock() + a.listMap[group] = permissions + return nil +} + +func (a *ACL) storePermissions(group, ownerAddress string) error { + a.lock.Lock() + defer a.lock.Unlock() + permissions := map[string]uint8{} + if _, ok := a.listMap[group]; ok { + permissions = a.listMap[group] + } else { + permissions[ownerAddress] = PermissionWrite + a.listMap[group] = permissions + } + data, err := json.Marshal(&permissions) + if err != nil { + return err + } + + f2 := f.NewFile("", a.c, a.f, utils.HexToAddress(ownerAddress), nil, a.logger) + topicString := utils.CombinePathAndFile(ownerAddress, group) + return f2.Upload(bytes.NewReader(data), topicString, int64(len(data)), f.MinBlockSize, 0, "/", "gzip", "") +} diff --git a/pkg/acl/acl/mock/controller.go b/pkg/acl/acl/mock/controller.go new file mode 100644 index 00000000..1d653ae4 --- /dev/null +++ b/pkg/acl/acl/mock/controller.go @@ -0,0 +1,74 @@ +package mock + +import ( + "sync" +) + +type ACL struct { + lock sync.Mutex + listMap map[string]map[string]map[string]uint8 +} + +func (a *ACL) CreateGroup(groupName, ownerAddress string) error { + a.lock.Lock() + defer a.lock.Unlock() + + a.listMap[ownerAddress] = map[string]map[string]uint8{} + a.listMap[ownerAddress][groupName] = map[string]uint8{} + return nil +} + +func (a *ACL) AddMember(groupName, ownerAddress, memberAddress string, permission uint8) error { + a.lock.Lock() + defer a.lock.Unlock() + a.listMap[ownerAddress][groupName][memberAddress] = permission + return nil +} + +func (a *ACL) RemoveMember(groupName, ownerAddress, memberAddress string) error { + a.lock.Lock() + defer a.lock.Unlock() + + delete(a.listMap[ownerAddress][groupName], memberAddress) + return nil +} + +func (a *ACL) RemoveGroup(groupName, ownerAddress string) error { + a.lock.Lock() + defer a.lock.Unlock() + delete(a.listMap[ownerAddress], groupName) + return nil +} + +func (a *ACL) GetGroupMembers(groupName, ownerAddress string) (map[string]uint8, error) { + a.lock.Lock() + defer a.lock.Unlock() + return a.listMap[ownerAddress][groupName], nil +} + +func (a *ACL) GetAllGroups(ownerAddress string) (map[string]map[string]uint8, error) { + a.lock.Lock() + defer a.lock.Unlock() + + return a.listMap[ownerAddress], nil +} + +func (a *ACL) UpdatePermission(groupName, ownerAddress, memberAddress string, permission uint8) error { + a.lock.Lock() + defer a.lock.Unlock() + + a.listMap[ownerAddress][groupName][memberAddress] = permission + return nil +} + +func (a *ACL) GetPermission(groupName, ownerAddress, memberAddress string) (uint8, error) { + a.lock.Lock() + defer a.lock.Unlock() + return a.listMap[ownerAddress][groupName][memberAddress], nil +} + +func NewMockACL() *ACL { + return &ACL{ + listMap: make(map[string]map[string]map[string]uint8), + } +} diff --git a/pkg/api/dir_chmod.go b/pkg/api/dir_chmod.go index b52a8437..f7766d59 100644 --- a/pkg/api/dir_chmod.go +++ b/pkg/api/dir_chmod.go @@ -13,9 +13,10 @@ import ( // DirModeRequest is used for changing dir mode type DirModeRequest struct { - PodName string `json:"podName,omitempty"` - DirPath string `json:"dirPath,omitempty"` - Mode string `json:"mode,omitempty"` + PodName string `json:"podName,omitempty"` + GroupName string `json:"groupName,omitempty"` + DirPath string `json:"dirPath,omitempty"` + Mode string `json:"mode,omitempty"` } // DirectoryModeHandler godoc @@ -49,11 +50,15 @@ func (h *Handler) DirectoryModeHandler(w http.ResponseWriter, r *http.Request) { return } - podName := chmodReq.PodName - if podName == "" { - h.logger.Errorf("dir chmod: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "dir chmod: \"podName\" argument missing"}) - return + driveName, isGroup := chmodReq.GroupName, true + if driveName == "" { + driveName = chmodReq.PodName + isGroup = false + if driveName == "" { + h.logger.Errorf("dir chmod: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "dir chmod: \"podName\" argument missing"}) + return + } } dirPath := chmodReq.DirPath @@ -90,7 +95,7 @@ func (h *Handler) DirectoryModeHandler(w http.ResponseWriter, r *http.Request) { return } - err = h.dfsAPI.ChmodDir(podName, dirPath, sessionId, uint32(mode)) + err = h.dfsAPI.ChmodDir(driveName, dirPath, sessionId, uint32(mode), isGroup) if err != nil { h.logger.Errorf("dir chmod: %v", err) jsonhttp.BadRequest(w, &response{Message: err.Error()}) diff --git a/pkg/api/dir_ls.go b/pkg/api/dir_ls.go index 48ab7e79..3efa182a 100644 --- a/pkg/api/dir_ls.go +++ b/pkg/api/dir_ls.go @@ -17,16 +17,15 @@ limitations under the License. package api import ( + "errors" "net/http" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" - - "resenje.org/jsonhttp" - "github.com/fairdatasociety/fairOS-dfs/pkg/dfs" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/file" p "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "resenje.org/jsonhttp" ) // ListFileResponse is used to list directories and files @@ -50,13 +49,25 @@ type ListFileResponse struct { // @Failure 500 {object} response // @Router /v1/dir/ls [get] func (h *Handler) DirectoryLsHandler(w http.ResponseWriter, r *http.Request) { - keys, ok := r.URL.Query()["podName"] - if !ok || len(keys[0]) < 1 { - h.logger.Errorf("ls: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "ls: \"podName\" argument missing"}) - return + driveName, isGroup := "", false + keys, ok := r.URL.Query()["groupName"] + if ok || (len(keys) == 1 && len(keys[0]) > 0) { + driveName = keys[0] + isGroup = true + } else { + keys, ok = r.URL.Query()["podName"] + if !ok || len(keys[0]) < 1 { + h.logger.Errorf("ls: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "ls: \"podName\" argument missing"}) + return + } + driveName = keys[0] + if driveName == "" { + h.logger.Errorf("ls: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "ls: \"podName\" argument missing"}) + return + } } - podName := keys[0] keys, ok = r.URL.Query()["dirPath"] if !ok || len(keys[0]) < 1 { @@ -80,15 +91,15 @@ func (h *Handler) DirectoryLsHandler(w http.ResponseWriter, r *http.Request) { } // list directory - dEntries, fEntries, err := h.dfsAPI.ListDir(podName, directory, sessionId) + dEntries, fEntries, err := h.dfsAPI.ListDir(driveName, directory, sessionId, isGroup) if err != nil { - if err == dfs.ErrPodNotOpen || err == dfs.ErrUserNotLoggedIn || - err == p.ErrPodNotOpened { + if errors.Is(err, dfs.ErrPodNotOpen) || errors.Is(err, dfs.ErrUserNotLoggedIn) || + errors.Is(err, p.ErrPodNotOpened) { h.logger.Errorf("ls: %v", err) jsonhttp.BadRequest(w, &response{Message: "ls: " + err.Error()}) return } - if err == dir.ErrDirectoryNotPresent { + if errors.Is(err, dir.ErrDirectoryNotPresent) { h.logger.Errorf("ls: %v", err) jsonhttp.NotFound(w, &response{Message: "ls: " + err.Error()}) return diff --git a/pkg/api/dir_mkdir.go b/pkg/api/dir_mkdir.go index 1cd9bb53..a532a022 100644 --- a/pkg/api/dir_mkdir.go +++ b/pkg/api/dir_mkdir.go @@ -18,6 +18,7 @@ package api import ( "encoding/json" + "errors" "net/http" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" @@ -31,6 +32,7 @@ import ( // DirRequest is used to create directory type DirRequest struct { PodName string `json:"podName,omitempty"` + GroupName string `json:"groupName,omitempty"` DirectoryPath string `json:"dirPath,omitempty"` } @@ -64,12 +66,15 @@ func (h *Handler) DirectoryMkdirHandler(w http.ResponseWriter, r *http.Request) jsonhttp.BadRequest(w, &response{Message: "mkdir: could not decode arguments"}) return } - - podName := fsReq.PodName - if podName == "" { - h.logger.Errorf("mkdir: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "mkdir: \"podName\" argument missing"}) - return + driveName, isGroup := fsReq.GroupName, true + if driveName == "" { + driveName = fsReq.PodName + isGroup = false + if driveName == "" { + h.logger.Errorf("mkdir: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "mkdir: \"podName\" argument missing"}) + return + } } dirToCreateWithPath := fsReq.DirectoryPath @@ -93,12 +98,12 @@ func (h *Handler) DirectoryMkdirHandler(w http.ResponseWriter, r *http.Request) } // make directory - err = h.dfsAPI.Mkdir(podName, dirToCreateWithPath, sessionId, 0) + err = h.dfsAPI.Mkdir(driveName, dirToCreateWithPath, sessionId, 0, isGroup) if err != nil { - if err == dfs.ErrPodNotOpen || err == dfs.ErrUserNotLoggedIn || - err == p.ErrInvalidDirectory || - err == p.ErrTooLongDirectoryName || - err == p.ErrPodNotOpened { + if errors.Is(err, dfs.ErrPodNotOpen) || errors.Is(err, dfs.ErrUserNotLoggedIn) || + errors.Is(err, p.ErrInvalidDirectory) || + errors.Is(err, p.ErrTooLongDirectoryName) || + errors.Is(err, p.ErrPodNotOpened) { h.logger.Errorf("mkdir: %v", err) jsonhttp.BadRequest(w, &response{Message: "mkdir: " + err.Error()}) return diff --git a/pkg/api/dir_present.go b/pkg/api/dir_present.go index c36b527b..031668df 100644 --- a/pkg/api/dir_present.go +++ b/pkg/api/dir_present.go @@ -45,13 +45,25 @@ type DirPresentResponse struct { // @Failure 500 {object} response // @Router /v1/dir/present [get] func (h *Handler) DirectoryPresentHandler(w http.ResponseWriter, r *http.Request) { - keys, ok := r.URL.Query()["podName"] - if !ok || len(keys[0]) < 1 { - h.logger.Errorf("dir present: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "dir present: \"podName\" argument missing"}) - return + driveName, isGroup := "", false + keys, ok := r.URL.Query()["groupName"] + if ok || (len(keys) == 1 && len(keys[0]) > 0) { + driveName = keys[0] + isGroup = true + } else { + keys, ok = r.URL.Query()["podName"] + if !ok || len(keys[0]) < 1 { + h.logger.Errorf("dir present: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "dir present: \"podName\" argument missing"}) + return + } + driveName = keys[0] + if driveName == "" { + h.logger.Errorf("dir present: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "dir present: \"podName\" argument missing"}) + return + } } - podName := keys[0] keys, ok = r.URL.Query()["dirPath"] if !ok || len(keys[0]) < 1 { @@ -75,7 +87,7 @@ func (h *Handler) DirectoryPresentHandler(w http.ResponseWriter, r *http.Request } // check if user is present - present, err := h.dfsAPI.IsDirPresent(podName, dirToCheck, sessionId) + present, err := h.dfsAPI.IsDirPresent(driveName, dirToCheck, sessionId, isGroup) if err != nil { jsonhttp.OK(w, &DirPresentResponse{ Present: present, diff --git a/pkg/api/dir_rename.go b/pkg/api/dir_rename.go index 84ec2eb6..b6938fcc 100644 --- a/pkg/api/dir_rename.go +++ b/pkg/api/dir_rename.go @@ -18,6 +18,7 @@ package api import ( "encoding/json" + "errors" "net/http" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" @@ -60,12 +61,15 @@ func (h *Handler) DirectoryRenameHandler(w http.ResponseWriter, r *http.Request) jsonhttp.BadRequest(w, &response{Message: "rename-dir: could not decode arguments"}) return } - - podName := renameReq.PodName - if podName == "" { - h.logger.Errorf("rename-dir: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "rename-dir: \"podName\" argument missing"}) - return + driveName, isGroup := renameReq.GroupName, true + if driveName == "" { + driveName = renameReq.PodName + isGroup = false + if driveName == "" { + h.logger.Errorf("rename-dir: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "rename-dir: \"podName\" argument missing"}) + return + } } oldPath := renameReq.OldPath @@ -96,12 +100,12 @@ func (h *Handler) DirectoryRenameHandler(w http.ResponseWriter, r *http.Request) } // make directory - err = h.dfsAPI.RenameDir(podName, oldPath, newPath, sessionId) + err = h.dfsAPI.RenameDir(driveName, oldPath, newPath, sessionId, isGroup) if err != nil { - if err == dfs.ErrPodNotOpen || err == dfs.ErrUserNotLoggedIn || - err == p.ErrInvalidDirectory || - err == p.ErrTooLongDirectoryName || - err == p.ErrPodNotOpened { + if errors.Is(err, dfs.ErrPodNotOpen) || errors.Is(err, dfs.ErrUserNotLoggedIn) || + errors.Is(err, p.ErrInvalidDirectory) || + errors.Is(err, p.ErrTooLongDirectoryName) || + errors.Is(err, p.ErrPodNotOpened) { h.logger.Errorf("rename-dir: %v", err) jsonhttp.BadRequest(w, &response{Message: "rename-dir: " + err.Error()}) return diff --git a/pkg/api/dir_rmdir.go b/pkg/api/dir_rmdir.go index 260cb2bf..c3aa488a 100644 --- a/pkg/api/dir_rmdir.go +++ b/pkg/api/dir_rmdir.go @@ -18,6 +18,7 @@ package api import ( "encoding/json" + "errors" "net/http" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" @@ -59,11 +60,15 @@ func (h *Handler) DirectoryRmdirHandler(w http.ResponseWriter, r *http.Request) return } - podName := fsReq.PodName - if podName == "" { - h.logger.Errorf("rmdir: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "rmdir: \"podName\" argument missing"}) - return + driveName, isGroup := fsReq.GroupName, true + if driveName == "" { + driveName = fsReq.PodName + isGroup = false + if driveName == "" { + h.logger.Errorf("rmdir: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "rmdir: \"podName\" argument missing"}) + return + } } dir := fsReq.DirectoryPath @@ -87,10 +92,10 @@ func (h *Handler) DirectoryRmdirHandler(w http.ResponseWriter, r *http.Request) } // remove directory - err = h.dfsAPI.RmDir(podName, dir, sessionId) + err = h.dfsAPI.RmDir(driveName, dir, sessionId, isGroup) if err != nil { - if err == dfs.ErrPodNotOpen || err == dfs.ErrUserNotLoggedIn || - err == p.ErrPodNotOpened { + if errors.Is(err, dfs.ErrUserNotLoggedIn) || errors.Is(err, dfs.ErrPodNotOpen) || + errors.Is(err, p.ErrPodNotOpened) { h.logger.Errorf("rmdir: %v", err) jsonhttp.BadRequest(w, &response{Message: "rmdir: " + err.Error()}) return diff --git a/pkg/api/dir_stat.go b/pkg/api/dir_stat.go index 1320802a..2eb1c26e 100644 --- a/pkg/api/dir_stat.go +++ b/pkg/api/dir_stat.go @@ -17,6 +17,7 @@ limitations under the License. package api import ( + "errors" "net/http" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" @@ -42,14 +43,25 @@ import ( // @Failure 500 {object} response // @Router /v1/dir/stat [get] func (h *Handler) DirectoryStatHandler(w http.ResponseWriter, r *http.Request) { - keys, ok := r.URL.Query()["podName"] - if !ok || len(keys[0]) < 1 { - h.logger.Errorf("dir: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "dir: \"podName\" argument missing"}) - return + driveName, isGroup := "", false + keys, ok := r.URL.Query()["groupName"] + if ok || (len(keys) == 1 && len(keys[0]) > 0) { + driveName = keys[0] + isGroup = true + } else { + keys, ok = r.URL.Query()["podName"] + if !ok || len(keys[0]) < 1 { + h.logger.Errorf("dir: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "dir: \"podName\" argument missing"}) + return + } + driveName = keys[0] + if driveName == "" { + h.logger.Errorf("dir: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "dir: \"podName\" argument missing"}) + return + } } - podName := keys[0] - keys, ok = r.URL.Query()["dirPath"] if !ok || len(keys[0]) < 1 { h.logger.Errorf("dir present: \"dirPath\" argument missing") @@ -72,10 +84,10 @@ func (h *Handler) DirectoryStatHandler(w http.ResponseWriter, r *http.Request) { } // stat directory - ds, err := h.dfsAPI.DirectoryStat(podName, dir, sessionId) + ds, err := h.dfsAPI.DirectoryStat(driveName, dir, sessionId, isGroup) if err != nil { - if err == dfs.ErrPodNotOpen || err == dfs.ErrUserNotLoggedIn || - err == p.ErrPodNotOpened { + if errors.Is(err, dfs.ErrPodNotOpen) || errors.Is(err, dfs.ErrUserNotLoggedIn) || + errors.Is(err, p.ErrPodNotOpened) { h.logger.Errorf("dir stat: %v", err) jsonhttp.BadRequest(w, &response{Message: "dir stat: " + err.Error()}) return diff --git a/pkg/api/file_chmod.go b/pkg/api/file_chmod.go index 9e5a811c..88026821 100644 --- a/pkg/api/file_chmod.go +++ b/pkg/api/file_chmod.go @@ -13,9 +13,10 @@ import ( // FileModeRequest is used to change file permission mode type FileModeRequest struct { - PodName string `json:"podName,omitempty"` - FilePath string `json:"filePath,omitempty"` - Mode string `json:"mode,omitempty"` + PodName string `json:"podName,omitempty"` + GroupName string `json:"groupName,omitempty"` + FilePath string `json:"filePath,omitempty"` + Mode string `json:"mode,omitempty"` } // FileModeHandler godoc @@ -48,12 +49,15 @@ func (h *Handler) FileModeHandler(w http.ResponseWriter, r *http.Request) { jsonhttp.BadRequest(w, &response{Message: "file chmod: could not decode arguments"}) return } - - podName := chmodReq.PodName - if podName == "" { - h.logger.Errorf("file chmod: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "file chmod: \"podName\" argument missing"}) - return + driveName, isGroup := chmodReq.GroupName, true + if driveName == "" { + driveName = chmodReq.PodName + isGroup = false + if driveName == "" { + h.logger.Errorf("file chmod: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "file chmod: \"podName\" argument missing"}) + return + } } filePath := chmodReq.FilePath @@ -90,7 +94,7 @@ func (h *Handler) FileModeHandler(w http.ResponseWriter, r *http.Request) { return } - err = h.dfsAPI.ChmodFile(podName, filePath, sessionId, uint32(mode)) + err = h.dfsAPI.ChmodFile(driveName, filePath, sessionId, uint32(mode), isGroup) if err != nil { h.logger.Errorf("file chmod: %v", err) jsonhttp.BadRequest(w, &response{Message: err.Error()}) diff --git a/pkg/api/file_delete.go b/pkg/api/file_delete.go index 0502ae68..07659ba6 100644 --- a/pkg/api/file_delete.go +++ b/pkg/api/file_delete.go @@ -18,6 +18,7 @@ package api import ( "encoding/json" + "errors" "net/http" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" @@ -29,8 +30,9 @@ import ( // FileDeleteRequest is used in the file delete request type FileDeleteRequest struct { - PodName string `json:"podName,omitempty"` - FilePath string `json:"filePath,omitempty"` + PodName string `json:"podName,omitempty"` + GroupName string `json:"groupName,omitempty"` + FilePath string `json:"filePath,omitempty"` } // FileDeleteHandler godoc @@ -64,12 +66,15 @@ func (h *Handler) FileDeleteHandler(w http.ResponseWriter, r *http.Request) { jsonhttp.BadRequest(w, &response{Message: "file delete: could not decode arguments"}) return } - - podName := fsReq.PodName - if podName == "" { - h.logger.Errorf("file delete: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "file delete: \"podName\" argument missing"}) - return + driveName, isGroup := fsReq.GroupName, true + if driveName == "" { + driveName = fsReq.PodName + isGroup = false + if driveName == "" { + h.logger.Errorf("file delete: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "file delete: \"podName\" argument missing"}) + return + } } podFileWithPath := fsReq.FilePath @@ -92,14 +97,14 @@ func (h *Handler) FileDeleteHandler(w http.ResponseWriter, r *http.Request) { return } // delete file - err = h.dfsAPI.DeleteFile(podName, podFileWithPath, sessionId) + err = h.dfsAPI.DeleteFile(driveName, podFileWithPath, sessionId, isGroup) if err != nil { - if err == dfs.ErrPodNotOpen { + if errors.Is(err, dfs.ErrPodNotOpen) { h.logger.Errorf("file delete: %v", err) jsonhttp.BadRequest(w, &response{Message: "file delete: " + err.Error()}) return } - if err == pod.ErrInvalidFile { + if errors.Is(err, pod.ErrInvalidFile) { h.logger.Errorf("file delete: %v", err) jsonhttp.NotFound(w, &response{Message: "file delete: " + err.Error()}) return diff --git a/pkg/api/file_download.go b/pkg/api/file_download.go index 901679af..066e5704 100644 --- a/pkg/api/file_download.go +++ b/pkg/api/file_download.go @@ -44,11 +44,24 @@ import ( // @Failure 500 {object} response // @Router /v1/file/download [post] func (h *Handler) FileDownloadHandlerPost(w http.ResponseWriter, r *http.Request) { - podName := r.FormValue("podName") - if podName == "" { - h.logger.Errorf("download: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "download: \"podName\" argument missing"}) - return + driveName, isGroup := "", false + keys, ok := r.URL.Query()["groupName"] + if ok || (len(keys) == 1 && len(keys[0]) > 0) { + driveName = keys[0] + isGroup = true + } else { + keys, ok := r.URL.Query()["podName"] + if !ok || len(keys[0]) < 1 { + h.logger.Errorf("download \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "download: \"podName\" argument missing"}) + return + } + driveName = keys[0] + if driveName == "" { + h.logger.Errorf("download: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "download: \"podName\" argument missing"}) + return + } } podFileWithPath := r.FormValue("filePath") @@ -58,7 +71,7 @@ func (h *Handler) FileDownloadHandlerPost(w http.ResponseWriter, r *http.Request return } - h.handleDownload(w, r, podName, podFileWithPath) + h.handleDownload(w, r, driveName, podFileWithPath, isGroup) } // FileDownloadHandlerGet godoc @@ -77,17 +90,24 @@ func (h *Handler) FileDownloadHandlerPost(w http.ResponseWriter, r *http.Request // @Failure 500 {object} response // @Router /v1/file/download [get] func (h *Handler) FileDownloadHandlerGet(w http.ResponseWriter, r *http.Request) { - keys, ok := r.URL.Query()["podName"] - if !ok || len(keys[0]) < 1 { - h.logger.Errorf("download \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "download: \"podName\" argument missing"}) - return - } - podName := keys[0] - if podName == "" { - h.logger.Errorf("download: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "download: \"podName\" argument missing"}) - return + driveName, isGroup := "", false + keys, ok := r.URL.Query()["groupName"] + if ok || (len(keys) == 1 && len(keys[0]) > 0) { + driveName = keys[0] + isGroup = true + } else { + keys, ok := r.URL.Query()["podName"] + if !ok || len(keys[0]) < 1 { + h.logger.Errorf("download \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "download: \"podName\" argument missing"}) + return + } + driveName = keys[0] + if driveName == "" { + h.logger.Errorf("download: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "download: \"podName\" argument missing"}) + return + } } keys, ok = r.URL.Query()["filePath"] @@ -103,10 +123,10 @@ func (h *Handler) FileDownloadHandlerGet(w http.ResponseWriter, r *http.Request) return } - h.handleDownload(w, r, podName, podFileWithPath) + h.handleDownload(w, r, driveName, podFileWithPath, isGroup) } -func (h *Handler) handleDownload(w http.ResponseWriter, r *http.Request, podName, podFileWithPath string) { +func (h *Handler) handleDownload(w http.ResponseWriter, r *http.Request, podName, podFileWithPath string, isGroup bool) { // get sessionId from request sessionId, err := auth.GetSessionIdFromRequest(r) if err != nil { @@ -121,7 +141,7 @@ func (h *Handler) handleDownload(w http.ResponseWriter, r *http.Request, podName } // download file from bee - reader, size, err := h.dfsAPI.DownloadFile(podName, podFileWithPath, sessionId) + reader, size, err := h.dfsAPI.DownloadFile(podName, podFileWithPath, sessionId, false) if err != nil { if err == dfs.ErrPodNotOpen { h.logger.Errorf("download: %v", err) diff --git a/pkg/api/file_rename.go b/pkg/api/file_rename.go index 57dbb25a..a7ae6923 100644 --- a/pkg/api/file_rename.go +++ b/pkg/api/file_rename.go @@ -18,6 +18,7 @@ package api import ( "encoding/json" + "errors" "net/http" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" @@ -61,11 +62,15 @@ func (h *Handler) FileRenameHandler(w http.ResponseWriter, r *http.Request) { return } - podName := renameReq.PodName - if podName == "" { - h.logger.Errorf("file rename: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "file rename: \"podName\" argument missing"}) - return + driveName, isGroup := renameReq.GroupName, true + if driveName == "" { + driveName = renameReq.PodName + isGroup = false + if driveName == "" { + h.logger.Errorf("file rename: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "file rename: \"podName\" argument missing"}) + return + } } podFileWithPath := renameReq.OldPath @@ -95,14 +100,14 @@ func (h *Handler) FileRenameHandler(w http.ResponseWriter, r *http.Request) { return } // rename file - err = h.dfsAPI.RenameFile(podName, podFileWithPath, newPodFileWithPath, sessionId) + err = h.dfsAPI.RenameFile(driveName, podFileWithPath, newPodFileWithPath, sessionId, isGroup) if err != nil { - if err == dfs.ErrPodNotOpen { + if errors.Is(err, dfs.ErrPodNotOpen) { h.logger.Errorf("file rename: %v", err) jsonhttp.BadRequest(w, &response{Message: "file rename: " + err.Error()}) return } - if err == pod.ErrInvalidFile { + if errors.Is(err, pod.ErrInvalidFile) { h.logger.Errorf("file rename: %v", err) jsonhttp.NotFound(w, &response{Message: "file rename: " + err.Error()}) return diff --git a/pkg/api/file_share.go b/pkg/api/file_share.go index a6ecb512..1a366e9f 100644 --- a/pkg/api/file_share.go +++ b/pkg/api/file_share.go @@ -38,6 +38,7 @@ type FileSharingReference struct { // FileShareRequest is the request to share a file type FileShareRequest struct { PodName string `json:"podName,omitempty"` + GroupName string `json:"groupName,omitempty"` FilePath string `json:"filePath,omitempty"` Destination string `json:"destUser,omitempty"` } @@ -72,20 +73,23 @@ func (h *Handler) FileShareHandler(w http.ResponseWriter, r *http.Request) { jsonhttp.BadRequest(w, &response{Message: "file share: could not decode arguments"}) return } - - podName := fsReq.PodName - if podName == "" { - h.logger.Errorf("file share: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "file share: \"podName\" argument missing"}) - return + driveName, isGroup := fsReq.GroupName, true + if driveName == "" { + driveName = fsReq.PodName + isGroup = false + if driveName == "" { + h.logger.Errorf("file share: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "file share: \"podName\" argument missing"}) + return + } } - podFileWithPath := fsReq.FilePath if podFileWithPath == "" { h.logger.Errorf("file share: \"filePath\" argument missing") jsonhttp.BadRequest(w, &response{Message: "file share: \"filePath\" argument missing"}) return } + destinationRef := fsReq.Destination if destinationRef == "" { h.logger.Errorf("file share: \"destUser\" argument missing") @@ -106,7 +110,7 @@ func (h *Handler) FileShareHandler(w http.ResponseWriter, r *http.Request) { return } - sharingRef, err := h.dfsAPI.ShareFile(podName, podFileWithPath, destinationRef, sessionId) + sharingRef, err := h.dfsAPI.ShareFile(driveName, podFileWithPath, destinationRef, sessionId, isGroup) if err != nil { h.logger.Errorf("file share: %v", err) jsonhttp.InternalServerError(w, &response{Message: "file share: " + err.Error()}) diff --git a/pkg/api/file_stat.go b/pkg/api/file_stat.go index 88d1de93..723cea66 100644 --- a/pkg/api/file_stat.go +++ b/pkg/api/file_stat.go @@ -17,6 +17,7 @@ limitations under the License. package api import ( + "errors" "net/http" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" @@ -42,17 +43,24 @@ import ( // @Failure 500 {object} response // @Router /v1/file/stat [get] func (h *Handler) FileStatHandler(w http.ResponseWriter, r *http.Request) { - keys, ok := r.URL.Query()["podName"] - if !ok || len(keys[0]) < 1 { - h.logger.Errorf("file stat: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "file stat: \"podName\" argument missing"}) - return - } - podName := keys[0] - if podName == "" { - h.logger.Errorf("file stat: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "file stat: \"podName\" argument missing"}) - return + driveName, isGroup := "", false + keys, ok := r.URL.Query()["groupName"] + if ok || (len(keys) == 1 && len(keys[0]) > 0) { + driveName = keys[0] + isGroup = true + } else { + keys, ok = r.URL.Query()["podName"] + if !ok || len(keys[0]) < 1 { + h.logger.Errorf("file stat: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "file stat: \"podName\" argument missing"}) + return + } + driveName = keys[0] + if driveName == "" { + h.logger.Errorf("file stat: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "file stat: \"podName\" argument missing"}) + return + } } keys, ok = r.URL.Query()["filePath"] @@ -82,9 +90,9 @@ func (h *Handler) FileStatHandler(w http.ResponseWriter, r *http.Request) { } // get file stat - stat, err := h.dfsAPI.FileStat(podName, podFileWithPath, sessionId) + stat, err := h.dfsAPI.FileStat(driveName, podFileWithPath, sessionId, isGroup) if err != nil { - if err == dfs.ErrPodNotOpen { + if errors.Is(err, dfs.ErrPodNotOpen) { h.logger.Errorf("file stat: %v", err) jsonhttp.BadRequest(w, &response{Message: "file stat: " + err.Error()}) return diff --git a/pkg/api/file_status.go b/pkg/api/file_status.go index 3fbeb64b..56d5c6ee 100644 --- a/pkg/api/file_status.go +++ b/pkg/api/file_status.go @@ -26,6 +26,7 @@ type StatusResponse struct { // @Accept json // @Produce */* // @Param podName query string true "pod name" +// @Param groupName query string true "group name" // @Param filePath query string true "file path" // @Param Cookie header string true "cookie parameter" // @Success 200 {array} StatusResponse @@ -33,17 +34,24 @@ type StatusResponse struct { // @Failure 500 {object} response // @Router /v1/file/status [get] func (h *Handler) FileStatusHandler(w http.ResponseWriter, r *http.Request) { - keys, ok := r.URL.Query()["podName"] - if !ok || len(keys[0]) < 1 { - h.logger.Errorf("status \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "status: \"podName\" argument missing"}) - return - } - podName := keys[0] - if podName == "" { - h.logger.Errorf("status: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "status: \"podName\" argument missing"}) - return + driveName, isGroup := "", false + keys, ok := r.URL.Query()["groupName"] + if ok || (len(keys) == 1 && len(keys[0]) > 0) { + driveName = keys[0] + isGroup = true + } else { + keys, ok = r.URL.Query()["podName"] + if !ok || len(keys[0]) < 1 { + h.logger.Errorf("status \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "status: \"podName\" argument missing"}) + return + } + driveName = keys[0] + if driveName == "" { + h.logger.Errorf("status: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "status: \"podName\" argument missing"}) + return + } } keys, ok = r.URL.Query()["filePath"] @@ -73,7 +81,7 @@ func (h *Handler) FileStatusHandler(w http.ResponseWriter, r *http.Request) { } // status of file - t, p, s, err := h.dfsAPI.StatusFile(podName, podFileWithPath, sessionId) + t, p, s, err := h.dfsAPI.StatusFile(driveName, podFileWithPath, sessionId, isGroup) if err != nil { if err == dfs.ErrPodNotOpen { h.logger.Errorf("status: %v", err) diff --git a/pkg/api/file_update.go b/pkg/api/file_update.go index b68b180f..53721735 100644 --- a/pkg/api/file_update.go +++ b/pkg/api/file_update.go @@ -55,12 +55,15 @@ func (h *Handler) FileUpdateHandler(w http.ResponseWriter, r *http.Request) { jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) return } - - podName := r.FormValue("podName") - if podName == "" { - h.logger.Errorf("file update: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "file update: \"podName\" argument missing"}) - return + driveName, isGroup := r.FormValue("groupName"), true + if driveName == "" { + isGroup = false + driveName = r.FormValue("podName") + if driveName == "" { + h.logger.Errorf("file update: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "file update: \"podName\" argument missing"}) + return + } } fileNameWithPath := r.FormValue("filePath") @@ -91,7 +94,7 @@ func (h *Handler) FileUpdateHandler(w http.ResponseWriter, r *http.Request) { } defer file.Close() - _, err = h.dfsAPI.WriteAtFile(podName, fileNameWithPath, sessionId, file, offset, false) + _, err = h.dfsAPI.WriteAtFile(driveName, fileNameWithPath, sessionId, file, offset, false, isGroup) if err != nil { h.logger.Errorf("file update: writeAt failed: %s", err.Error()) jsonhttp.BadRequest(w, &response{Message: "file update: writeAt failed: " + err.Error()}) diff --git a/pkg/api/file_upload.go b/pkg/api/file_upload.go index 914fb36c..9eb16183 100644 --- a/pkg/api/file_upload.go +++ b/pkg/api/file_upload.go @@ -17,10 +17,13 @@ limitations under the License. package api import ( + "errors" "mime/multipart" "net/http" "strconv" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" "github.com/dustin/go-humanize" @@ -66,13 +69,16 @@ const ( // @Failure 500 {object} response // @Router /v1/file/upload [Post] func (h *Handler) FileUploadHandler(w http.ResponseWriter, r *http.Request) { - podName := r.FormValue("podName") - if podName == "" { - h.logger.Errorf("file upload: \"podName\" argument missing") - jsonhttp.BadRequest(w, &response{Message: "file upload: \"podName\" argument missing"}) - return + driveName, isGroup := r.FormValue("groupName"), true + if driveName == "" { + isGroup = false + driveName = r.FormValue("podName") + if driveName == "" { + h.logger.Errorf("file upload: \"podName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "file upload: \"podName\" argument missing"}) + return + } } - podPath := r.FormValue("dirPath") if podPath == "" { h.logger.Errorf("file upload: \"dirPath\" argument missing") @@ -150,16 +156,21 @@ func (h *Handler) FileUploadHandler(w http.ResponseWriter, r *http.Request) { responses = append(responses, UploadResponse{FileName: file.Filename, Message: err.Error()}) continue } - err = h.handleFileUpload(podName, file.Filename, sessionId, file.Size, fd, podPath, compression, uint32(bs), overwrite) + err = h.handleFileUpload(driveName, file.Filename, sessionId, file.Size, fd, podPath, compression, uint32(bs), overwrite, isGroup) if err != nil { - if err == dfs.ErrPodNotOpen { + if errors.Is(err, pod.ErrInvalidPodName) { + h.logger.Errorf("file upload: %v", err) + jsonhttp.NotFound(w, &response{Message: "file upload: " + err.Error()}) + return + } + if errors.Is(err, dfs.ErrPodNotOpen) { h.logger.Errorf("file upload: %v", err) jsonhttp.BadRequest(w, &response{Message: "file upload: " + err.Error()}) return } h.logger.Errorf("file upload: %v", err) - responses = append(responses, UploadResponse{FileName: file.Filename, Message: err.Error()}) - continue + jsonhttp.InternalServerError(w, &response{Message: "file upload: " + err.Error()}) + return } responses = append(responses, UploadResponse{FileName: file.Filename, Message: "uploaded successfully"}) } @@ -170,7 +181,7 @@ func (h *Handler) FileUploadHandler(w http.ResponseWriter, r *http.Request) { }) } -func (h *Handler) handleFileUpload(podName, podFileName, sessionId string, fileSize int64, f multipart.File, podPath, compression string, blockSize uint32, overwrite bool) error { +func (h *Handler) handleFileUpload(podName, podFileName, sessionId string, fileSize int64, f multipart.File, podPath, compression string, blockSize uint32, overwrite, isGroup bool) error { defer f.Close() - return h.dfsAPI.UploadFile(podName, podFileName, sessionId, fileSize, f, podPath, compression, blockSize, 0, overwrite) + return h.dfsAPI.UploadFile(podName, podFileName, sessionId, fileSize, f, podPath, compression, blockSize, 0, overwrite, isGroup) } diff --git a/pkg/api/group_accept_invite.go b/pkg/api/group_accept_invite.go new file mode 100644 index 00000000..e44b9397 --- /dev/null +++ b/pkg/api/group_accept_invite.go @@ -0,0 +1,75 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "resenje.org/jsonhttp" +) + +// GroupInviteRequest is the request to accept a group invite +type GroupInviteRequest struct { + Reference string `json:"reference,omitempty"` +} + +// GroupAcceptInviteHandler godoc +// +// @Summary Accept group membersion +// @Description GroupAcceptInviteHandler is the api handler to accept a group invite +// @ID group-accept-invite-handler +// @Tags group +// @Accept json +// @Produce json +// @Param reference body GroupInviteRequest true "reference of the invite" +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} response +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/accept [post] +func (h *Handler) GroupAcceptInviteHandler(w http.ResponseWriter, r *http.Request) { + contentType := r.Header.Get("Content-Type") + if contentType != jsonContentType { + h.logger.Errorf("group accept: invalid request body type") + jsonhttp.BadRequest(w, &response{Message: "group accept: invalid request body type"}) + return + } + + decoder := json.NewDecoder(r.Body) + var req GroupInviteRequest + err := decoder.Decode(&req) + if err != nil { + h.logger.Errorf("group accept: could not decode arguments") + jsonhttp.BadRequest(w, &response{Message: "group accept: could not decode arguments"}) + return + } + + reference := req.Reference + if reference == "" { + h.logger.Errorf("group accept: \"reference\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group accept: \"reference\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + err = h.dfsAPI.AcceptGroupInvite(sessionId, []byte(reference)) + if err != nil { + h.logger.Errorf("group accept: failed to accept group invite: %v", err) + jsonhttp.InternalServerError(w, &response{Message: "group accept: failed to accept group invite"}) + return + } + + jsonhttp.OK(w, &response{Message: "group invite accepted"}) +} diff --git a/pkg/api/group_add_member.go b/pkg/api/group_add_member.go new file mode 100644 index 00000000..ad87e86a --- /dev/null +++ b/pkg/api/group_add_member.go @@ -0,0 +1,99 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "resenje.org/jsonhttp" +) + +// GroupAddMemberRequest is the request to add a member to a group +type GroupAddMemberRequest struct { + GroupName string `json:"groupName,omitempty"` + Member string `json:"member,omitempty"` + Permission uint8 `json:"permission,omitempty"` +} + +// GroupAddMemberResponse is the response to add a member to a group +type GroupAddMemberResponse struct { + Reference string `json:"invite,omitempty"` +} + +// GroupAddMemberHandler godoc +// +// @Summary Add member to group +// @Description GroupAddMemberHandler is the api handler to add a member to a group +// @ID group-add-member-handler +// @Tags group +// @Accept json +// @Produce json +// @Param group_request body GroupAddMemberRequest true "group name, member name and permission" +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} GroupAddMemberResponse +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/add [post] +func (h *Handler) GroupAddMemberHandler(w http.ResponseWriter, r *http.Request) { + contentType := r.Header.Get("Content-Type") + if contentType != jsonContentType { + h.logger.Errorf("group add: invalid request body type") + jsonhttp.BadRequest(w, &response{Message: "group add: invalid request body type"}) + return + } + + decoder := json.NewDecoder(r.Body) + var req GroupAddMemberRequest + err := decoder.Decode(&req) + if err != nil { + h.logger.Errorf("group add: could not decode arguments") + jsonhttp.BadRequest(w, &response{Message: "group add: could not decode arguments"}) + return + } + + group := req.GroupName + if group == "" { + h.logger.Errorf("group add: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group add: \"groupName\" argument missing"}) + return + } + + member := req.Member + if member == "" { + h.logger.Errorf("group add: \"member\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group add: \"member\" argument missing"}) + return + } + + permission := req.Permission + if permission == 0 { + h.logger.Errorf("group add: \"permission\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group add: \"permission\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + ref, err := h.dfsAPI.AddMember(sessionId, group, member, permission) + if err != nil { + h.logger.Errorf("group add: ", err) + jsonhttp.InternalServerError(w, &response{Message: "group add: " + err.Error()}) + return + } + + w.Header().Set("Content-Type", " application/json") + jsonhttp.OK(w, &GroupAddMemberResponse{ + Reference: string(ref), + }) +} diff --git a/pkg/api/group_close.go b/pkg/api/group_close.go new file mode 100644 index 00000000..7e52e6cc --- /dev/null +++ b/pkg/api/group_close.go @@ -0,0 +1,70 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "resenje.org/jsonhttp" +) + +// GroupCloseHandler godoc +// +// @Summary Close group +// @Description GroupCloseHandler is the api handler to close a group +// @ID group-close-handler +// @Tags group +// @Accept json +// @Produce json +// @Param groupRequest body GroupNameRequest true "group name" +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} response +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/close [post] +func (h *Handler) GroupCloseHandler(w http.ResponseWriter, r *http.Request) { + contentType := r.Header.Get("Content-Type") + if contentType != jsonContentType { + h.logger.Errorf("group close: invalid request body type") + jsonhttp.BadRequest(w, &response{Message: "group close: invalid request body type"}) + return + } + + decoder := json.NewDecoder(r.Body) + var req GroupNameRequest + err := decoder.Decode(&req) + if err != nil { + h.logger.Errorf("group close: could not decode arguments") + jsonhttp.BadRequest(w, &response{Message: "group close: could not decode arguments"}) + return + } + + group := req.GroupName + if group == "" { + h.logger.Errorf("group close: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group close: \"groupName\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + err = h.dfsAPI.CloseGroup(group, sessionId) + if err != nil { + h.logger.Errorf("group close: ", err) + jsonhttp.InternalServerError(w, &response{Message: err.Error()}) + return + } + + jsonhttp.OK(w, &response{Message: "group closed"}) +} diff --git a/pkg/api/group_create.go b/pkg/api/group_create.go new file mode 100644 index 00000000..29d2ff8b --- /dev/null +++ b/pkg/api/group_create.go @@ -0,0 +1,89 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "github.com/fairdatasociety/fairOS-dfs/pkg/dfs" + p "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "resenje.org/jsonhttp" +) + +// GroupNameRequest is the request to create a group +type GroupNameRequest struct { + GroupName string `json:"groupName,omitempty"` +} + +// GroupCreateHandler godoc +// +// @Summary Create group +// @Description GroupCreateHandler is the api handler to create a new group +// @ID group-create-handler +// @Tags group +// @Accept json +// @Produce json +// @Param group_request body GroupNameRequest true "group name" +// @Param Cookie header string true "cookie parameter" +// @Success 201 {object} response +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/new [post] +func (h *Handler) GroupCreateHandler(w http.ResponseWriter, r *http.Request) { + contentType := r.Header.Get("Content-Type") + if contentType != jsonContentType { + h.logger.Errorf("group new: invalid request body type") + jsonhttp.BadRequest(w, &response{Message: "group new: invalid request body type"}) + return + } + + decoder := json.NewDecoder(r.Body) + var req GroupNameRequest + err := decoder.Decode(&req) + if err != nil { + h.logger.Errorf("group new: could not decode arguments") + jsonhttp.BadRequest(w, &response{Message: "group new: could not decode arguments"}) + return + } + + group := req.GroupName + if group == "" { + h.logger.Errorf("group new: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group new: \"groupName\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + _, err = h.dfsAPI.CreateGroup(sessionId, group) + if err != nil { + fmt.Println(err) + if errors.Is(err, dfs.ErrUserNotLoggedIn) || + errors.Is(err, p.ErrInvalidPodName) || + errors.Is(err, p.ErrTooLongPodName) || + errors.Is(err, p.ErrPodAlreadyExists) || + errors.Is(err, p.ErrMaxPodsReached) { + h.logger.Errorf("group new: %v", err) + jsonhttp.BadRequest(w, &response{Message: "group new: " + err.Error()}) + return + } + h.logger.Errorf("group new: %v", err) + jsonhttp.InternalServerError(w, &response{Message: "group new: " + err.Error()}) + return + } + + jsonhttp.Created(w, &response{Message: "group created successfully"}) +} diff --git a/pkg/api/group_delete.go b/pkg/api/group_delete.go new file mode 100644 index 00000000..3f6dedc4 --- /dev/null +++ b/pkg/api/group_delete.go @@ -0,0 +1,70 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "resenje.org/jsonhttp" +) + +// GroupDeleteHandler godoc +// +// @Summary Delete group +// @Description GroupDeleteHandler is the api handler to delete a new group +// @ID group-delete-handler +// @Tags group +// @Accept json +// @Produce json +// @Param group_request body GroupNameRequest true "group name" +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} response +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/delete [delete] +func (h *Handler) GroupDeleteHandler(w http.ResponseWriter, r *http.Request) { + contentType := r.Header.Get("Content-Type") + if contentType != jsonContentType { + h.logger.Errorf("group delete: invalid request body type") + jsonhttp.BadRequest(w, &response{Message: "group delete: invalid request body type"}) + return + } + + decoder := json.NewDecoder(r.Body) + var req GroupNameRequest + err := decoder.Decode(&req) + if err != nil { + h.logger.Errorf("group delete: could not decode arguments") + jsonhttp.BadRequest(w, &response{Message: "group delete: could not decode arguments"}) + return + } + + group := req.GroupName + if group == "" { + h.logger.Errorf("group delete: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group delete: \"groupName\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + err = h.dfsAPI.RemoveGroup(sessionId, group) + if err != nil { + h.logger.Errorf("group delete: %v", err) + jsonhttp.InternalServerError(w, &response{Message: "group delete: " + err.Error()}) + return + } + + jsonhttp.OK(w, &response{Message: "group deleted successfully"}) +} diff --git a/pkg/api/group_delete_shared.go b/pkg/api/group_delete_shared.go new file mode 100644 index 00000000..50f1737f --- /dev/null +++ b/pkg/api/group_delete_shared.go @@ -0,0 +1,70 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "resenje.org/jsonhttp" +) + +// GroupDeleteSharedHandler godoc +// +// @Summary Delete shared group +// @Description GroupDeleteSharedHandler is the api handler to delete a shared group +// @ID group-delete-shared-handler +// @Tags group +// @Accept json +// @Produce json +// @Param group_request body GroupNameRequest true "group name" +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} response +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/delete-shared [delete] +func (h *Handler) GroupDeleteSharedHandler(w http.ResponseWriter, r *http.Request) { + contentType := r.Header.Get("Content-Type") + if contentType != jsonContentType { + h.logger.Errorf("group delete shared: invalid request body type") + jsonhttp.BadRequest(w, &response{Message: "group delete shared: invalid request body type"}) + return + } + + decoder := json.NewDecoder(r.Body) + var req GroupNameRequest + err := decoder.Decode(&req) + if err != nil { + h.logger.Errorf("group delete shared: could not decode arguments") + jsonhttp.BadRequest(w, &response{Message: "group delete shared: could not decode arguments"}) + return + } + + group := req.GroupName + if group == "" { + h.logger.Errorf("group delete shared: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group delete shared: \"groupName\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + err = h.dfsAPI.RemoveSharedGroup(sessionId, group) + if err != nil { + h.logger.Errorf("group delete shared: %v", err) + jsonhttp.InternalServerError(w, &response{Message: "group delete shared: " + err.Error()}) + return + } + + jsonhttp.OK(w, &response{Message: "group deleted successfully"}) +} diff --git a/pkg/api/group_get_members.go b/pkg/api/group_get_members.go new file mode 100644 index 00000000..f7b85251 --- /dev/null +++ b/pkg/api/group_get_members.go @@ -0,0 +1,66 @@ +package api + +import ( + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "resenje.org/jsonhttp" +) + +// GroupMembersResponse is the response to get group members +type GroupMembersResponse struct { + Members map[string]uint8 `json:"members,omitempty"` +} + +// GroupGetMembers gets the members of a group +// +// @Summary Get group members +// @Description GroupGetMembers is the api handler to get the members of a group +// @ID group-get-members +// @Tags group +// @Accept json +// @Produce json +// @Param groupName query string true "group name" +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} GroupMembersResponse +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/members [get] +func (h *Handler) GroupGetMembers(w http.ResponseWriter, r *http.Request) { + keys, ok := r.URL.Query()["groupName"] + if !ok || len(keys[0]) < 1 { + h.logger.Errorf("group members: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group members: \"groupName\" argument missing"}) + return + } + groupName := keys[0] + if groupName == "" { + h.logger.Errorf("group members: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group members: \"groupName\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + members, err := h.dfsAPI.GetGroupMembers(sessionId, groupName) + if err != nil { + h.logger.Errorf("group members: failed to get group members: %v", err) + jsonhttp.InternalServerError(w, &response{Message: "group members: failed to get group members"}) + return + } + + jsonhttp.OK(w, &GroupMembersResponse{ + Members: members, + }) +} diff --git a/pkg/api/group_get_permission.go b/pkg/api/group_get_permission.go new file mode 100644 index 00000000..3df0ee4e --- /dev/null +++ b/pkg/api/group_get_permission.go @@ -0,0 +1,64 @@ +package api + +import ( + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "resenje.org/jsonhttp" +) + +// GroupPermissionResponse represents the response of the group permission request +type GroupPermissionResponse struct { + Permission uint8 `json:"permission"` +} + +// GroupGetPermission returns the permission of the loggedin user in the group +// +// @Summary Get the permission of the user in the group +// @Description Get the permission of the user in the group +// @Tags group +// @Accept json +// @Produce json +// @Param groupName query string true "Group name" +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} GroupPermissionResponse +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/permission [get] +func (h *Handler) GroupGetPermission(w http.ResponseWriter, r *http.Request) { + keys, ok := r.URL.Query()["groupName"] + if !ok || len(keys[0]) < 1 { + h.logger.Errorf("group permission: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group permission: \"groupName\" argument missing"}) + return + } + groupName := keys[0] + if groupName == "" { + h.logger.Errorf("group permission: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group permission: \"groupName\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + permission, err := h.dfsAPI.GetPermission(sessionId, groupName) + if err != nil { + h.logger.Errorf("group permission: failed to get group permission: %v", err) + jsonhttp.InternalServerError(w, &response{Message: "group permission: failed to get group permission"}) + return + } + jsonhttp.OK(w, &GroupPermissionResponse{ + Permission: permission, + }) +} diff --git a/pkg/api/group_list.go b/pkg/api/group_list.go new file mode 100644 index 00000000..bec78469 --- /dev/null +++ b/pkg/api/group_list.go @@ -0,0 +1,49 @@ +package api + +import ( + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "resenje.org/jsonhttp" +) + +// GroupListResponse represents the response of the group list request +type GroupListResponse pod.GroupList + +// GroupListHandler is the handler for group list API +// +// @Summary List groups +// @Description List groups +// @ID group_list +// @Tags group +// @Accept json +// @Produce json +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} GroupListResponse +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/ls [get] +func (h *Handler) GroupListHandler(w http.ResponseWriter, r *http.Request) { + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + groupList, err := h.dfsAPI.ListGroups(sessionId) + if err != nil { + h.logger.Errorf("ListGroups failed: ", err) + jsonhttp.InternalServerError(w, &response{Message: err.Error()}) + return + } + + jsonhttp.OK(w, groupList) +} diff --git a/pkg/api/group_open.go b/pkg/api/group_open.go new file mode 100644 index 00000000..355cfc60 --- /dev/null +++ b/pkg/api/group_open.go @@ -0,0 +1,70 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "resenje.org/jsonhttp" +) + +// GroupOpenHandler godoc +// +// @Summary Open group +// @Description GroupOpenHandler is the api handler to open a group +// @ID group-open +// @Tags group +// @Accept json +// @Produce json +// @Param group_request body GroupNameRequest true "group name" +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} response +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/open [post] +func (h *Handler) GroupOpenHandler(w http.ResponseWriter, r *http.Request) { + contentType := r.Header.Get("Content-Type") + if contentType != jsonContentType { + h.logger.Errorf("group open: invalid request body type") + jsonhttp.BadRequest(w, &response{Message: "group open: invalid request body type"}) + return + } + + decoder := json.NewDecoder(r.Body) + var req GroupNameRequest + err := decoder.Decode(&req) + if err != nil { + h.logger.Errorf("group open: could not decode arguments") + jsonhttp.BadRequest(w, &response{Message: "group open: could not decode arguments"}) + return + } + + group := req.GroupName + if group == "" { + h.logger.Errorf("group open: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group open: \"groupName\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + _, err = h.dfsAPI.OpenGroup(group, sessionId) + if err != nil { + h.logger.Errorf("group open failed: ", err) + jsonhttp.InternalServerError(w, &response{Message: "group open failed"}) + return + } + + jsonhttp.OK(w, &response{Message: "group open successful"}) +} diff --git a/pkg/api/group_remove_member.go b/pkg/api/group_remove_member.go new file mode 100644 index 00000000..432a9d4a --- /dev/null +++ b/pkg/api/group_remove_member.go @@ -0,0 +1,83 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "resenje.org/jsonhttp" +) + +// GroupRemoveMemberRequest is the request to remove a member from a group +type GroupRemoveMemberRequest struct { + GroupName string `json:"groupName,omitempty"` + Member string `json:"member,omitempty"` +} + +// GroupRemoveMemberHandler godoc +// +// @Summary Remove member from group +// @Description GroupRemoveMemberHandler is the api handler to remove a member from a group +// @ID group-remove-member-handler +// @Tags group +// @Accept json +// @Produce json +// @Param group_request body GroupRemoveMemberRequest true "group name and member name" +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} response +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/remove [post] +func (h *Handler) GroupRemoveMemberHandler(w http.ResponseWriter, r *http.Request) { + contentType := r.Header.Get("Content-Type") + if contentType != jsonContentType { + h.logger.Errorf("group remove: invalid request body type") + jsonhttp.BadRequest(w, &response{Message: "group remove: invalid request body type"}) + return + } + + decoder := json.NewDecoder(r.Body) + var req GroupRemoveMemberRequest + err := decoder.Decode(&req) + if err != nil { + h.logger.Errorf("group remove: could not decode arguments") + jsonhttp.BadRequest(w, &response{Message: "group remove: could not decode arguments"}) + return + } + + group := req.GroupName + if group == "" { + h.logger.Errorf("group remove: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group remove: \"groupName\" argument missing"}) + return + } + + member := req.Member + if member == "" { + h.logger.Errorf("group remove: \"member\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group remove: \"member\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + err = h.dfsAPI.RemoveMember(group, member, sessionId) + if err != nil { + h.logger.Errorf("group remove: failed to remove member from group: %v", err) + jsonhttp.InternalServerError(w, &response{Message: "group remove: failed to remove member from group"}) + return + } + + jsonhttp.OK(w, &response{Message: "member removed from group"}) +} diff --git a/pkg/api/group_update_permission.go b/pkg/api/group_update_permission.go new file mode 100644 index 00000000..42a55fde --- /dev/null +++ b/pkg/api/group_update_permission.go @@ -0,0 +1,84 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "resenje.org/jsonhttp" +) + +// GroupUpdatePermissionHandler godoc +// +// @Summary Update group permission +// @Description GroupUpdatePermissionHandler is the api handler to update a group permission +// @ID group-update-permission-handler +// @Tags group +// @Accept json +// @Produce json +// @Param group_request body GroupAddMemberRequest true "group name, member name and permission" +// @Param Cookie header string true "cookie parameter" +// @Success 200 {object} response +// @Failure 400 {object} response +// @Failure 500 {object} response +// @Router /v1/group/update-permission [post] +func (h *Handler) GroupUpdatePermissionHandler(w http.ResponseWriter, r *http.Request) { + contentType := r.Header.Get("Content-Type") + if contentType != jsonContentType { + h.logger.Errorf("group update permission: invalid request body type") + jsonhttp.BadRequest(w, &response{Message: "group update permission: invalid request body type"}) + return + } + + decoder := json.NewDecoder(r.Body) + var req GroupAddMemberRequest + err := decoder.Decode(&req) + if err != nil { + h.logger.Errorf("group update permission: could not decode arguments") + jsonhttp.BadRequest(w, &response{Message: "group update permission: could not decode arguments"}) + return + } + + group := req.GroupName + if group == "" { + h.logger.Errorf("group update permission: \"groupName\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group update permission: \"groupName\" argument missing"}) + return + } + + member := req.Member + if member == "" { + h.logger.Errorf("group update permission: \"member\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group update permission: \"member\" argument missing"}) + return + } + + permission := req.Permission + if permission == 0 { + h.logger.Errorf("group update permission: \"permission\" argument missing") + jsonhttp.BadRequest(w, &response{Message: "group update permission: \"permission\" argument missing"}) + return + } + + // get sessionId from request + sessionId, err := auth.GetSessionIdFromRequest(r) + if err != nil { + h.logger.Errorf("sessionId parse failed: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + if sessionId == "" { + h.logger.Error("sessionId not set: ", err) + jsonhttp.BadRequest(w, &response{Message: ErrUnauthorized.Error()}) + return + } + + err = h.dfsAPI.UpdatePermission(sessionId, group, member, permission) + if err != nil { + h.logger.Errorf("group update permission: %v", err) + jsonhttp.InternalServerError(w, &response{Message: err.Error()}) + return + } + + jsonhttp.OK(w, &response{Message: "group permission updated successfully"}) +} diff --git a/pkg/api/handler.go b/pkg/api/handler.go index 3ff378bd..9186da06 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -18,35 +18,72 @@ package api import ( "context" + "fmt" + "time" "github.com/fairdatasociety/fairOS-dfs/pkg/contracts" "github.com/fairdatasociety/fairOS-dfs/pkg/dfs" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" ) +const ( + defaultFeedCacheSize = -1 +) + +var errInvalidDuration = fmt.Errorf("invalid duration") + // Handler is the api handler type Handler struct { - ctx context.Context - cancel context.CancelFunc - dfsAPI *dfs.API - logger logging.Logger - + ctx context.Context + cancel context.CancelFunc + dfsAPI *dfs.API + logger logging.Logger whitelistedOrigins []string cookieDomain string } +type Options struct { + BeeApiEndpoint string + CookieDomain string + Stamp string + WhitelistedOrigins []string + EnsConfig *contracts.ENSConfig + SubscriptionConfig *contracts.SubscriptionConfig + Logger logging.Logger + FeedCacheSize int + FeedCacheTTL string +} + // New returns a new handler -func New(ctx context.Context, beeApi, cookieDomain, postageBlockId string, whitelistedOrigins []string, ensConfig *contracts.ENSConfig, subscriptionConfig *contracts.SubscriptionConfig, logger logging.Logger) (*Handler, error) { - api, err := dfs.NewDfsAPI(ctx, beeApi, postageBlockId, ensConfig, subscriptionConfig, logger) +func New(ctx context.Context, opts *Options) (*Handler, error) { + dfsOpts := &dfs.Options{ + BeeApiEndpoint: opts.BeeApiEndpoint, + Stamp: opts.Stamp, + EnsConfig: opts.EnsConfig, + SubscriptionConfig: opts.SubscriptionConfig, + Logger: opts.Logger, + } + if opts.FeedCacheSize == 0 { + opts.FeedCacheSize = defaultFeedCacheSize + } else { + dfsOpts.FeedCacheSize = opts.FeedCacheSize + + ttl, err := time.ParseDuration(opts.FeedCacheTTL) + if err != nil { + return nil, errInvalidDuration + } + dfsOpts.FeedCacheTTL = ttl + } + api, err := dfs.NewDfsAPI(ctx, dfsOpts) if err != nil { return nil, err } newContext, cancel := context.WithCancel(ctx) return &Handler{ dfsAPI: api, - logger: logger, - whitelistedOrigins: whitelistedOrigins, - cookieDomain: cookieDomain, + logger: opts.Logger, + whitelistedOrigins: opts.WhitelistedOrigins, + cookieDomain: opts.CookieDomain, ctx: newContext, cancel: cancel, }, nil diff --git a/pkg/api/pod_delete.go b/pkg/api/pod_delete.go index ea0dda84..24f9efbc 100644 --- a/pkg/api/pod_delete.go +++ b/pkg/api/pod_delete.go @@ -18,6 +18,7 @@ package api import ( "encoding/json" + "errors" "net/http" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" @@ -81,7 +82,7 @@ func (h *Handler) PodDeleteHandler(w http.ResponseWriter, r *http.Request) { // delete pod err = h.dfsAPI.DeletePod(podName, sessionId) if err != nil { - if err == dfs.ErrUserNotLoggedIn { + if errors.Is(err, dfs.ErrUserNotLoggedIn) { h.logger.Errorf("delete pod: %v", err) jsonhttp.BadRequest(w, &response{Message: "delete pod: " + err.Error()}) return diff --git a/pkg/api/pod_open.go b/pkg/api/pod_open.go index 9f54247a..143b2c80 100644 --- a/pkg/api/pod_open.go +++ b/pkg/api/pod_open.go @@ -18,6 +18,7 @@ package api import ( "encoding/json" + "errors" "net/http" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" @@ -80,8 +81,8 @@ func (h *Handler) PodOpenHandler(w http.ResponseWriter, r *http.Request) { // open pod _, err = h.dfsAPI.OpenPod(pod, sessionId) if err != nil { - if err == dfs.ErrUserNotLoggedIn || - err == p.ErrInvalidPodName { + if errors.Is(err, dfs.ErrUserNotLoggedIn) || + errors.Is(err, p.ErrInvalidPodName) { h.logger.Errorf("pod open: %v", err) jsonhttp.NotFound(w, &response{Message: "pod open: " + err.Error()}) return diff --git a/pkg/api/user_signup.go b/pkg/api/user_signup.go index e728121d..f262bd82 100644 --- a/pkg/api/user_signup.go +++ b/pkg/api/user_signup.go @@ -101,7 +101,7 @@ func (h *Handler) UserSignupV2Handler(w http.ResponseWriter, r *http.Request) { jsonhttp.PaymentRequired(w, &UserSignupResponse{ Address: signUp.Address, Mnemonic: signUp.Mnemonic, - Message: eth.ErrInsufficientBalance.Error(), + Message: eth.ErrInsufficientBalance.Error() + " for creating account on fairOS-dfs and not related with bee wallet.", }) return } diff --git a/pkg/api/ws.go b/pkg/api/ws.go index d15db5c5..43723337 100644 --- a/pkg/api/ws.go +++ b/pkg/api/ws.go @@ -694,7 +694,7 @@ func (h *Handler) handleEvents(conn *websocket.Conn) error { respondWithError(res, err) continue } - err = h.dfsAPI.Mkdir(fsReq.PodName, fsReq.DirectoryPath, sessionID, 0) + err = h.dfsAPI.Mkdir(fsReq.PodName, fsReq.DirectoryPath, sessionID, 0, false) if err != nil { respondWithError(res, err) continue @@ -726,7 +726,7 @@ func (h *Handler) handleEvents(conn *websocket.Conn) error { respondWithError(res, err) continue } - err = h.dfsAPI.RmDir(fsReq.PodName, fsReq.DirectoryPath, sessionID) + err = h.dfsAPI.RmDir(fsReq.PodName, fsReq.DirectoryPath, sessionID, false) if err != nil { respondWithError(res, err) continue @@ -758,7 +758,7 @@ func (h *Handler) handleEvents(conn *websocket.Conn) error { respondWithError(res, err) continue } - dEntries, fEntries, err := h.dfsAPI.ListDir(fsReq.PodName, fsReq.DirectoryPath, sessionID) + dEntries, fEntries, err := h.dfsAPI.ListDir(fsReq.PodName, fsReq.DirectoryPath, sessionID, false) if err != nil { respondWithError(res, err) continue @@ -797,7 +797,7 @@ func (h *Handler) handleEvents(conn *websocket.Conn) error { respondWithError(res, err) continue } - ds, err := h.dfsAPI.DirectoryStat(fsReq.PodName, fsReq.DirectoryPath, sessionID) + ds, err := h.dfsAPI.DirectoryStat(fsReq.PodName, fsReq.DirectoryPath, sessionID, false) if err != nil { respondWithError(res, err) continue @@ -827,7 +827,7 @@ func (h *Handler) handleEvents(conn *websocket.Conn) error { respondWithError(res, err) continue } - present, err := h.dfsAPI.IsDirPresent(fsReq.PodName, fsReq.DirectoryPath, sessionID) + present, err := h.dfsAPI.IsDirPresent(fsReq.PodName, fsReq.DirectoryPath, sessionID, false) if err != nil { respondWithError(res, err) continue @@ -952,7 +952,7 @@ func (h *Handler) handleEvents(conn *websocket.Conn) error { respondWithError(res, err) continue } - data, n, err := h.dfsAPI.DownloadFile(fsReq.PodName, fsReq.Filepath, sessionID) + data, n, err := h.dfsAPI.DownloadFile(fsReq.PodName, fsReq.Filepath, sessionID, false) if err != nil { respondWithError(res, err) continue @@ -1077,7 +1077,7 @@ func (h *Handler) handleEvents(conn *websocket.Conn) error { respondWithError(res, err) continue } - err = h.dfsAPI.UploadFile(fsReq.PodName, fileName, sessionID, int64(len(data.Bytes())), data, fsReq.DirPath, compression, uint32(bs), 0, fsReq.Overwrite) + err = h.dfsAPI.UploadFile(fsReq.PodName, fileName, sessionID, int64(len(data.Bytes())), data, fsReq.DirPath, compression, uint32(bs), 0, fsReq.Overwrite, false) if err != nil { respondWithError(res, err) continue @@ -1107,7 +1107,7 @@ func (h *Handler) handleEvents(conn *websocket.Conn) error { respondWithError(res, err) continue } - sharingRef, err := h.dfsAPI.ShareFile(fsReq.PodName, fsReq.DirectoryPath, fsReq.Destination, sessionID) + sharingRef, err := h.dfsAPI.ShareFile(fsReq.PodName, fsReq.DirectoryPath, fsReq.Destination, sessionID, false) if err != nil { respondWithError(res, err) continue @@ -1201,7 +1201,7 @@ func (h *Handler) handleEvents(conn *websocket.Conn) error { respondWithError(res, err) continue } - err = h.dfsAPI.DeleteFile(fsReq.PodName, fsReq.FilePath, sessionID) + err = h.dfsAPI.DeleteFile(fsReq.PodName, fsReq.FilePath, sessionID, false) if err != nil { respondWithError(res, err) continue @@ -1233,7 +1233,7 @@ func (h *Handler) handleEvents(conn *websocket.Conn) error { respondWithError(res, err) continue } - stat, err := h.dfsAPI.FileStat(fsReq.PodName, fsReq.DirectoryPath, sessionID) + stat, err := h.dfsAPI.FileStat(fsReq.PodName, fsReq.DirectoryPath, sessionID, false) if err != nil { respondWithError(res, err) continue diff --git a/pkg/auth/jwt/jwt.go b/pkg/auth/jwt/jwt.go index ea8af127..0cbe3c9d 100644 --- a/pkg/auth/jwt/jwt.go +++ b/pkg/auth/jwt/jwt.go @@ -44,7 +44,7 @@ func GenerateToken(sessionId string) (string, error) { return token.SignedString(secret) } -func Parse(tokenStr string) (string, error) { +func parse(tokenStr string) (string, error) { claims := &Claims{} token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) { return secret, nil @@ -77,5 +77,5 @@ func GetSessionIdFromToken(request *http.Request) (sessionId string, err error) if tokenStr == "" { return "", ErrNoTokenInRequest } - return Parse(tokenStr) + return parse(tokenStr) } diff --git a/pkg/blockstore/bee/client.go b/pkg/blockstore/bee/client.go index 29ce9702..30ef8371 100644 --- a/pkg/blockstore/bee/client.go +++ b/pkg/blockstore/bee/client.go @@ -19,7 +19,6 @@ package bee import ( "bytes" "context" - "encoding/hex" "encoding/json" "errors" "fmt" @@ -31,31 +30,31 @@ import ( "github.com/ethersphere/bee/pkg/swarm" bmtlegacy "github.com/ethersphere/bmt/legacy" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" - lru "github.com/hashicorp/golang-lru" + lru "github.com/hashicorp/golang-lru/v2/expirable" "github.com/sirupsen/logrus" "golang.org/x/crypto/sha3" ) const ( - maxIdleConnections = 20 - maxConnectionsPerHost = 256 - requestTimeout = 6000 - chunkCacheSize = 1024 - uploadBlockCacheSize = 100 - downloadBlockCacheSize = 100 - healthUrl = "/health" - chunkUploadDownloadUrl = "/chunks" - bytesUploadDownloadUrl = "/bytes" - bzzUrl = "/bzz" - tagsUrl = "/tags" - pinsUrl = "/pins/" - _ = pinsUrl - swarmPinHeader = "Swarm-Pin" - swarmEncryptHeader = "Swarm-Encrypt" - swarmPostageBatchId = "Swarm-Postage-Batch-Id" - // swarmDeferredUploadHeader = "Swarm-Deferred-Upload" - swarmTagHeader = "Swarm-Tag" - contentTypeHeader = "Content-Type" + maxIdleConnections = 20 + maxConnectionsPerHost = 256 + requestTimeout = 6000 + chunkCacheSize = 1024 + uploadBlockCacheSize = 100 + downloadBlockCacheSize = 100 + healthUrl = "/health" + chunkUploadDownloadUrl = "/chunks" + bytesUploadDownloadUrl = "/bytes" + bzzUrl = "/bzz" + tagsUrl = "/tags" + pinsUrl = "/pins/" + _ = pinsUrl + swarmPinHeader = "Swarm-Pin" + swarmEncryptHeader = "Swarm-Encrypt" + swarmPostageBatchId = "Swarm-Postage-Batch-Id" + swarmDeferredUploadHeader = "Swarm-Deferred-Upload" + swarmTagHeader = "Swarm-Tag" + contentTypeHeader = "Content-Type" ) // Client is a bee http client that satisfies blockstore.Client @@ -63,9 +62,9 @@ type Client struct { url string client *http.Client hasher *bmtlegacy.Hasher - chunkCache *lru.Cache - uploadBlockCache *lru.Cache - downloadBlockCache *lru.Cache + chunkCache *lru.LRU[string, []byte] + uploadBlockCache *lru.LRU[string, []byte] + downloadBlockCache *lru.LRU[string, []byte] postageBlockId string logger logging.Logger isProxy bool @@ -100,19 +99,9 @@ type beeError struct { // NewBeeClient creates a new client which connects to the Swarm bee node to access the Swarm network. func NewBeeClient(apiUrl, postageBlockId string, shouldPin bool, logger logging.Logger) *Client { p := bmtlegacy.NewTreePool(hashFunc, swarm.Branches, bmtlegacy.PoolSize) - cache, err := lru.New(chunkCacheSize) - if err != nil { - logger.Warningf("could not initialise chunkCache. system will be slow") - } - uploadBlockCache, err := lru.New(uploadBlockCacheSize) - if err != nil { - logger.Warningf("could not initialise blockCache. system will be slow") - } - downloadBlockCache, err := lru.New(downloadBlockCacheSize) - if err != nil { - logger.Warningf("could not initialise blockCache. system will be slow") - } - + cache := lru.NewLRU(chunkCacheSize, func(key string, value []byte) {}, 0) + uploadBlockCache := lru.NewLRU(uploadBlockCacheSize, func(key string, value []byte) {}, 0) + downloadBlockCache := lru.NewLRU(downloadBlockCacheSize, func(key string, value []byte) {}, 0) return &Client{ url: apiUrl, client: createHTTPClient(), @@ -163,14 +152,14 @@ func (s *Client) checkBee(isProxy bool) (string, error) { if err != nil { return "", err } - response, err := s.client.Do(req) + req.Close = true + // skipcq: GO-S2307 + response, err := s.Do(req) if err != nil { return "", err } - // skipcq: GO-S2307 defer response.Body.Close() - req.Close = true data, err := io.ReadAll(response.Body) if err != nil { return "", err @@ -178,6 +167,11 @@ func (s *Client) checkBee(isProxy bool) (string, error) { return string(data), nil } +// Do dispatches the HTTP request to the network +func (s *Client) Do(req *http.Request) (*http.Response, error) { + return s.client.Do(req) +} + // UploadSOC is used construct and send a Single Owner Chunk to the Swarm bee client. func (s *Client) UploadSOC(owner, id, signature string, data []byte) (address []byte, err error) { to := time.Now() @@ -188,11 +182,12 @@ func (s *Client) UploadSOC(owner, id, signature string, data []byte) (address [] if err != nil { return nil, err } + req.Close = true // the postage block id to store the SOC chunk req.Header.Set(swarmPostageBatchId, s.postageBlockId) - - // req.Header.Set(swarmDeferredUploadHeader, "false") + req.Header.Set(contentTypeHeader, "application/octet-stream") + req.Header.Set(swarmDeferredUploadHeader, "true") // TODO change this in the future when we have some alternative to pin SOC // This is a temporary fix to force soc pinning @@ -200,7 +195,7 @@ func (s *Client) UploadSOC(owner, id, signature string, data []byte) (address [] req.Header.Set(swarmPinHeader, "true") } - response, err := s.client.Do(req) + response, err := s.Do(req) if err != nil { return nil, err } @@ -208,8 +203,6 @@ func (s *Client) UploadSOC(owner, id, signature string, data []byte) (address [] // skipcq: GO-S2307 defer response.Body.Close() - req.Close = true - addrData, err := io.ReadAll(response.Body) if err != nil { return nil, errors.New("error uploading data") @@ -230,9 +223,6 @@ func (s *Client) UploadSOC(owner, id, signature string, data []byte) (address [] return nil, err } - if s.inChunkCache(addrResp.Reference.String()) { - s.addToChunkCache(addrResp.Reference.String(), data) - } fields := logrus.Fields{ "reference": addrResp.Reference.String(), "duration": time.Since(to).String(), @@ -249,25 +239,26 @@ func (s *Client) UploadChunk(ch swarm.Chunk) (address []byte, err error) { if err != nil { return nil, err } + req.Close = true if s.shouldPin { req.Header.Set(swarmPinHeader, "true") } + req.Header.Set(contentTypeHeader, "application/octet-stream") + // the postage block id to store the chunk req.Header.Set(swarmPostageBatchId, s.postageBlockId) - // req.Header.Set(swarmDeferredUploadHeader, "false") + req.Header.Set(swarmDeferredUploadHeader, "true") - response, err := s.client.Do(req) + response, err := s.Do(req) if err != nil { return nil, err } // skipcq: GO-S2307 defer response.Body.Close() - req.Close = true - addrData, err := io.ReadAll(response.Body) if err != nil { return nil, errors.New("error uploading data") @@ -288,9 +279,6 @@ func (s *Client) UploadChunk(ch swarm.Chunk) (address []byte, err error) { return nil, err } - if s.inChunkCache(ch.Address().String()) { - s.addToChunkCache(ch.Address().String(), ch.Data()) - } fields := logrus.Fields{ "reference": ch.Address().String(), "duration": time.Since(to).String(), @@ -304,9 +292,6 @@ func (s *Client) UploadChunk(ch swarm.Chunk) (address []byte, err error) { func (s *Client) DownloadChunk(ctx context.Context, address []byte) (data []byte, err error) { to := time.Now() addrString := swarm.NewAddress(address).String() - if s.inChunkCache(addrString) { - return s.getFromChunkCache(swarm.NewAddress(address).String()), nil - } path := chunkUploadDownloadUrl + "/" + addrString fullUrl := fmt.Sprintf(s.url + path) @@ -314,18 +299,17 @@ func (s *Client) DownloadChunk(ctx context.Context, address []byte) (data []byte if err != nil { return nil, err } + req.Close = true req = req.WithContext(ctx) - response, err := s.client.Do(req) + response, err := s.Do(req) if err != nil { return nil, err } // skipcq: GO-S2307 defer response.Body.Close() - req.Close = true - if response.StatusCode != http.StatusOK { return nil, errors.New("error downloading data") } @@ -334,8 +318,6 @@ func (s *Client) DownloadChunk(ctx context.Context, address []byte) (data []byte if err != nil { return nil, errors.New("error downloading data") } - - s.addToChunkCache(addrString, data) fields := logrus.Fields{ "reference": addrString, "duration": time.Since(to).String(), @@ -358,6 +340,9 @@ func (s *Client) UploadBlob(data []byte, tag uint32, encrypt bool) (address []by if err != nil { return nil, err } + req.Close = true + + req.Header.Set(contentTypeHeader, "application/octet-stream") if s.shouldPin { req.Header.Set(swarmPinHeader, "true") @@ -374,17 +359,15 @@ func (s *Client) UploadBlob(data []byte, tag uint32, encrypt bool) (address []by // the postage block id to store the blob req.Header.Set(swarmPostageBatchId, s.postageBlockId) - // req.Header.Set(swarmDeferredUploadHeader, "false") + req.Header.Set(swarmDeferredUploadHeader, "true") - response, err := s.client.Do(req) + response, err := s.Do(req) if err != nil { return nil, err } // skipcq: GO-S2307 defer response.Body.Close() - req.Close = true - respData, err := io.ReadAll(response.Body) if err != nil { return nil, errors.New("error uploading blob") @@ -411,10 +394,8 @@ func (s *Client) UploadBlob(data []byte, tag uint32, encrypt bool) (address []by } s.logger.WithFields(fields).Log(logrus.DebugLevel, "upload blob: ") - // add the data and ref if itis not in cache - if !s.inBlockCache(s.uploadBlockCache, string(data)) { - s.addToBlockCache(s.uploadBlockCache, string(data), resp.Reference.Bytes()) - } + // add the data in cache + s.addToBlockCache(s.uploadBlockCache, string(data), resp.Reference.Bytes()) return resp.Reference.Bytes(), nil } @@ -434,16 +415,15 @@ func (s *Client) DownloadBlob(address []byte) ([]byte, int, error) { if err != nil { return nil, http.StatusNotFound, err } + req.Close = true - response, err := s.client.Do(req) + response, err := s.Do(req) if err != nil { return nil, http.StatusNotFound, err } // skipcq: GO-S2307 defer response.Body.Close() - req.Close = true - respData, err := io.ReadAll(response.Body) if err != nil { return nil, response.StatusCode, errors.New("error downloading blob") @@ -481,19 +461,18 @@ func (s *Client) UploadBzz(data []byte, fileName string) (address []byte, err er if err != nil { return nil, err } + req.Close = true req.Header.Set(swarmPostageBatchId, s.postageBlockId) req.Header.Set(contentTypeHeader, "application/json") - response, err := s.client.Do(req) + response, err := s.Do(req) if err != nil { return nil, err } // skipcq: GO-S2307 defer response.Body.Close() - req.Close = true - respData, err := io.ReadAll(response.Body) if err != nil { return nil, errors.New("error downloading bzz") @@ -540,16 +519,15 @@ func (s *Client) DownloadBzz(address []byte) ([]byte, int, error) { if err != nil { return nil, http.StatusNotFound, err } + req.Close = true - response, err := s.client.Do(req) + response, err := s.Do(req) if err != nil { return nil, http.StatusNotFound, err } // skipcq: GO-S2307 defer response.Body.Close() - req.Close = true - respData, err := io.ReadAll(response.Body) if err != nil { return nil, response.StatusCode, errors.New("error downloading bzz") @@ -579,40 +557,42 @@ func (s *Client) DownloadBzz(address []byte) ([]byte, int, error) { } // DeleteReference unpins a reference so that it will be garbage collected by the Swarm network. -func (*Client) DeleteReference(address []byte) error { - // TODO uncomment after unpinning is fixed - _ = address - /* - to := time.Now() - addrString := swarm.NewAddress(address).String() +func (s *Client) DeleteReference(address []byte) error { + if !s.shouldPin { + return nil + } + to := time.Now() + addrString := swarm.NewAddress(address).String() - fullUrl := s.url + pinsUrl + addrString - req, err := http.NewRequest(http.MethodDelete, fullUrl, http.NoBody) - if err != nil { - return err - } + fullUrl := s.url + pinsUrl + addrString + req, err := http.NewRequest(http.MethodDelete, fullUrl, http.NoBody) + if err != nil { + return err + } + req.Close = true + + response, err := s.Do(req) + if err != nil { + return err + } + defer response.Body.Close() - response, err := s.client.Do(req) + if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotFound { + respData, err := io.ReadAll(response.Body) if err != nil { return err } - defer response.Body.Close() - - req.Close = true - if response.StatusCode != http.StatusOK { - respData, err := io.ReadAll(response.Body) - if err != nil { - return err - } - return fmt.Errorf("failed to unpin reference : %s", respData) - } + return fmt.Errorf("failed to unpin reference : %s", respData) + } else { + _, _ = io.Copy(io.Discard, response.Body) + } + + fields := logrus.Fields{ + "reference": addrString, + "duration": time.Since(to).String(), + } + s.logger.WithFields(fields).Log(logrus.DebugLevel, "delete chunk: ") - fields := logrus.Fields{ - "reference": addrString, - "duration": time.Since(to).String(), - } - s.logger.WithFields(fields).Log(logrus.DebugLevel, "delete chunk: ") - */ return nil } @@ -639,16 +619,15 @@ func (s *Client) CreateTag(address []byte) (uint32, error) { if err != nil { return 0, err } + req.Close = true - response, err := s.client.Do(req) + response, err := s.Do(req) if err != nil { return 0, err } // skipcq: GO-S2307 defer response.Body.Close() - req.Close = true - respData, err := io.ReadAll(response.Body) if err != nil { return 0, errors.New("error create tag") @@ -693,16 +672,15 @@ func (s *Client) GetTag(tag uint32) (int64, int64, int64, error) { if err != nil { return 0, 0, 0, err } + req.Close = true - response, err := s.client.Do(req) + response, err := s.Do(req) if err != nil { return 0, 0, 0, err } // skipcq: GO-S2307 defer response.Body.Close() - req.Close = true - respData, err := io.ReadAll(response.Body) if err != nil { return 0, 0, 0, errors.New("error getting tag") @@ -743,52 +721,22 @@ func createHTTPClient() *http.Client { return client } -func (s *Client) addToChunkCache(key string, value []byte) { - if s.chunkCache != nil { - s.chunkCache.Add(key, hex.EncodeToString(value)) - } -} - -func (s *Client) inChunkCache(key string) bool { - if s.chunkCache != nil { - return s.chunkCache.Contains(key) - } - return false -} - -func (s *Client) getFromChunkCache(key string) []byte { - if s.chunkCache != nil { - value, ok := s.chunkCache.Get(key) - if ok { - data, err := hex.DecodeString(fmt.Sprintf("%v", value)) - if err != nil { - return nil - } - return data - } - return nil - } - return nil -} - -func (*Client) addToBlockCache(cache *lru.Cache, key string, value []byte) { +func (*Client) addToBlockCache(cache *lru.LRU[string, []byte], key string, value []byte) { if cache != nil { cache.Add(key, value) } } -func (*Client) inBlockCache(cache *lru.Cache, key string) bool { - if cache != nil { - return cache.Contains(key) - } - return false +func (*Client) inBlockCache(cache *lru.LRU[string, []byte], key string) bool { + _, in := cache.Get(key) + return in } -func (*Client) getFromBlockCache(cache *lru.Cache, key string) []byte { +func (*Client) getFromBlockCache(cache *lru.LRU[string, []byte], key string) []byte { if cache != nil { value, ok := cache.Get(key) if ok { - return value.([]byte) + return value } return nil } diff --git a/pkg/blockstore/bee/mock/client.go b/pkg/blockstore/bee/mock/client.go index c4ba87f9..3eb86b41 100644 --- a/pkg/blockstore/bee/mock/client.go +++ b/pkg/blockstore/bee/mock/client.go @@ -1,171 +1,340 @@ -/* -Copyright © 2020 FairOS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -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. -*/ - package mock import ( "context" + "crypto/ecdsa" "crypto/rand" "encoding/hex" - "errors" - "fmt" + "math/big" "net/http" + "net/http/httptest" "sync" + "testing" "time" - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/ethersphere/bee/pkg/storageincentives/redistribution" - "github.com/ethersphere/bee/pkg/soc" + "github.com/ethereum/go-ethereum/common" + accountingmock "github.com/ethersphere/bee/pkg/accounting/mock" + "github.com/ethersphere/bee/pkg/api" + "github.com/ethersphere/bee/pkg/auth" + mockauth "github.com/ethersphere/bee/pkg/auth/mock" + "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/feeds" + "github.com/ethersphere/bee/pkg/log" + p2pmock "github.com/ethersphere/bee/pkg/p2p/mock" + "github.com/ethersphere/bee/pkg/pingpong" + "github.com/ethersphere/bee/pkg/postage" + mockbatchstore "github.com/ethersphere/bee/pkg/postage/batchstore/mock" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + "github.com/ethersphere/bee/pkg/postage/postagecontract" + contractMock "github.com/ethersphere/bee/pkg/postage/postagecontract/mock" + "github.com/ethersphere/bee/pkg/pss" + "github.com/ethersphere/bee/pkg/resolver" + resolverMock "github.com/ethersphere/bee/pkg/resolver/mock" + "github.com/ethersphere/bee/pkg/settlement/pseudosettle" + chequebookmock "github.com/ethersphere/bee/pkg/settlement/swap/chequebook/mock" + "github.com/ethersphere/bee/pkg/settlement/swap/erc20" + erc20mock "github.com/ethersphere/bee/pkg/settlement/swap/erc20/mock" + swapmock "github.com/ethersphere/bee/pkg/settlement/swap/mock" + statestore "github.com/ethersphere/bee/pkg/statestore/mock" + "github.com/ethersphere/bee/pkg/status" + "github.com/ethersphere/bee/pkg/steward" + "github.com/ethersphere/bee/pkg/storage" + "github.com/ethersphere/bee/pkg/storage/inmemstore" + "github.com/ethersphere/bee/pkg/storageincentives" + "github.com/ethersphere/bee/pkg/storageincentives/staking" + mock2 "github.com/ethersphere/bee/pkg/storageincentives/staking/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/pkg/topology/lightnode" + topologymock "github.com/ethersphere/bee/pkg/topology/mock" + "github.com/ethersphere/bee/pkg/tracing" + "github.com/ethersphere/bee/pkg/transaction" + "github.com/ethersphere/bee/pkg/transaction/backendmock" + transactionmock "github.com/ethersphere/bee/pkg/transaction/mock" + "github.com/ethersphere/bee/pkg/util/testutil" ) -// BeeClient is a mock bee client -type BeeClient struct { - storer map[string][]byte - tagStorer map[uint32]int64 - storerMu sync.RWMutex -} +var ( + batchOk = make([]byte, 32) + BatchOkStr string +) -// NewMockBeeClient returns a mock bee client -func NewMockBeeClient() *BeeClient { - return &BeeClient{ - storer: make(map[string][]byte), - tagStorer: make(map[uint32]int64), - storerMu: sync.RWMutex{}, - } +// nolint:gochecknoinits +func init() { + _, _ = rand.Read(batchOk) + + BatchOkStr = hex.EncodeToString(batchOk) } -// CheckConnection checks connection -func (*BeeClient) CheckConnection() bool { - return true +type TestServerOptions struct { + Storer api.Storer + StateStorer storage.StateStorer + Resolver resolver.Interface + Pss pss.Interface + WsPath string + WsPingPeriod time.Duration + Logger log.Logger + PreventRedirect bool + Feeds feeds.Factory + CORSAllowedOrigins []string + PostageContract postagecontract.Interface + StakingContract staking.Contract + Post postage.Service + Steward steward.Interface + WsHeaders http.Header + Authenticator auth.Authenticator + DebugAPI bool + Restricted bool + DirectUpload bool + Probe *api.Probe + + Overlay swarm.Address + PublicKey ecdsa.PublicKey + PSSPublicKey ecdsa.PublicKey + EthereumAddress common.Address + BlockTime time.Duration + P2P *p2pmock.Service + Pingpong pingpong.Interface + TopologyOpts []topologymock.Option + AccountingOpts []accountingmock.Option + ChequebookOpts []chequebookmock.Option + SwapOpts []swapmock.Option + TransactionOpts []transactionmock.Option + + BatchStore postage.Storer + SyncStatus func() (bool, error) + + BackendOpts []backendmock.Option + Erc20Opts []erc20mock.Option + BeeMode api.BeeNodeMode + RedistributionAgent *storageincentives.Agent + NodeStatus *status.Service } -// UploadSOC uploads soc into swarm -func (m *BeeClient) UploadSOC(owner, id, signature string, data []byte) (address []byte, err error) { - m.storerMu.Lock() - defer m.storerMu.Unlock() - ch, err := utils.NewChunkWithoutSpan(data) - if err != nil { - return nil, err +func NewTestBeeServer(t *testing.T, o TestServerOptions) string { + t.Helper() + pk, _ := crypto.GenerateSecp256k1Key() + signer := crypto.NewDefaultSigner(pk) + + if o.Logger == nil { + o.Logger = log.Noop + } + if o.Resolver == nil { + o.Resolver = resolverMock.NewResolver() } - idBytes, err := hex.DecodeString(id) - if err != nil { - return nil, err + if o.WsPingPeriod == 0 { + o.WsPingPeriod = 60 * time.Second } - ownerBytes, err := hex.DecodeString(owner) - if err != nil { - return nil, err + if o.Post == nil { + o.Post = mockpost.New() } - signatureBytes, err := hex.DecodeString(signature) - if err != nil { - return nil, err + if o.BatchStore == nil { + o.BatchStore = mockbatchstore.New(mockbatchstore.WithAcceptAllExistsFunc()) // default is with accept-all Exists() func } - signed, err := soc.NewSigned(idBytes, ch, ownerBytes, signatureBytes) - if err != nil { - return nil, err + if o.SyncStatus == nil { + o.SyncStatus = func() (bool, error) { return true, nil } } - signedChunk, err := signed.Chunk() - if err != nil { - return nil, err + if o.Authenticator == nil { + o.Authenticator = &mockauth.Auth{ + EnforceFunc: func(_, _, _ string) (bool, error) { + return true, nil + }, + } } - if !soc.Valid(signedChunk) { - return nil, fmt.Errorf("soc chunk failed in validation") + + topologyDriver := topologymock.NewTopologyDriver(o.TopologyOpts...) + acc := accountingmock.NewAccounting(o.AccountingOpts...) + settlement := swapmock.New(o.SwapOpts...) + chequebook := chequebookmock.NewChequebook(o.ChequebookOpts...) + ln := lightnode.NewContainer(o.Overlay) + + transaction := transactionmock.New(o.TransactionOpts...) + + storeRecipient := statestore.NewStateStore() + recipient := pseudosettle.New(nil, o.Logger, storeRecipient, nil, big.NewInt(10000), big.NewInt(10000), o.P2P) + + if o.StateStorer == nil { + o.StateStorer = storeRecipient } - m.storer[signedChunk.Address().String()] = signedChunk.Data() - return signedChunk.Address().Bytes(), nil + erc20 := erc20mock.New(o.Erc20Opts...) + backend := backendmock.New(o.BackendOpts...) + + var extraOpts = api.ExtraOptions{ + TopologyDriver: topologyDriver, + Accounting: acc, + Pseudosettle: recipient, + LightNodes: ln, + Swap: settlement, + Chequebook: chequebook, + Pingpong: o.Pingpong, + BlockTime: o.BlockTime, + Storer: o.Storer, + Resolver: o.Resolver, + Pss: o.Pss, + FeedFactory: o.Feeds, + Post: o.Post, + PostageContract: o.PostageContract, + Steward: o.Steward, + SyncStatus: o.SyncStatus, + Staking: o.StakingContract, + NodeStatus: o.NodeStatus, + } + + // By default bee mode is set to full mode. + if o.BeeMode == api.UnknownMode { + o.BeeMode = api.FullMode + } + o.CORSAllowedOrigins = append(o.CORSAllowedOrigins, "*") + s := api.New(o.PublicKey, o.PSSPublicKey, o.EthereumAddress, o.Logger, transaction, o.BatchStore, o.BeeMode, true, true, backend, o.CORSAllowedOrigins, inmemstore.New()) + testutil.CleanupCloser(t, s) + + s.SetP2P(o.P2P) + + if o.RedistributionAgent == nil { + o.RedistributionAgent, _ = createRedistributionAgentService(t, o.Overlay, o.StateStorer, erc20, transaction, backend, o.BatchStore) + s.SetRedistributionAgent(o.RedistributionAgent) + } + testutil.CleanupCloser(t, o.RedistributionAgent) + + s.SetSwarmAddress(&o.Overlay) + s.SetProbe(o.Probe) + + noOpTracer, tracerCloser, _ := tracing.NewTracer(&tracing.Options{ + Enabled: false, + }) + testutil.CleanupCloser(t, tracerCloser) + + s.Configure(signer, o.Authenticator, noOpTracer, api.Options{ + CORSAllowedOrigins: o.CORSAllowedOrigins, + WsPingPeriod: o.WsPingPeriod, + Restricted: o.Restricted, + }, extraOpts, 1, erc20) + + if o.DebugAPI { + s.MountTechnicalDebug() + s.MountDebug(false) + } else { + s.MountAPI() + } + + ts := httptest.NewServer(s) + t.Cleanup(ts.Close) + return ts.URL } -// UploadChunk into swarm -func (m *BeeClient) UploadChunk(ch swarm.Chunk) (address []byte, err error) { - m.storerMu.Lock() - defer m.storerMu.Unlock() - m.storer[ch.Address().String()] = ch.Data() - return ch.Address().Bytes(), nil +func createRedistributionAgentService( + t *testing.T, + addr swarm.Address, + storer storage.StateStorer, + erc20Service erc20.Service, + tranService transaction.Service, + backend storageincentives.ChainBackend, + chainStateGetter postage.ChainStateGetter, +) (*storageincentives.Agent, error) { + t.Helper() + + const blocksPerRound uint64 = 12 + const blocksPerPhase uint64 = 4 + postageContract := contractMock.New(contractMock.WithExpiresBatchesFunc(func(context.Context) error { + return nil + }), + ) + stakingContract := mock2.New(mock2.WithIsFrozen(func(context.Context, uint64) (bool, error) { + return true, nil + })) + contract := &mockContract{} + + return storageincentives.New( + addr, + common.Address{}, + backend, + contract, + postageContract, + stakingContract, + mockstorer.NewReserve(), + func() bool { return true }, + time.Millisecond*10, + blocksPerRound, + blocksPerPhase, + storer, + chainStateGetter, + erc20Service, + tranService, + &mockHealth{}, + log.Noop, + ) } -// DownloadChunk from swarm -func (m *BeeClient) DownloadChunk(_ context.Context, address []byte) (data []byte, err error) { - m.storerMu.Lock() - defer m.storerMu.Unlock() - if data, ok := m.storer[swarm.NewAddress(address).String()]; ok { - return data, nil +type contractCall int + +func (c contractCall) String() string { + switch c { + case isWinnerCall: + return "isWinnerCall" + case revealCall: + return "revealCall" + case commitCall: + return "commitCall" + case claimCall: + return "claimCall" } - return nil, fmt.Errorf("error downloading data") + return "unknown" } -// UploadBlob into swarm -func (m *BeeClient) UploadBlob(data []byte, tag uint32, _ bool) (address []byte, err error) { - m.storerMu.Lock() - defer m.storerMu.Unlock() - address = make([]byte, 32) - _, err = rand.Read(address) - newChunks := int64(len(data) / 4000) - if newChunks == 0 { - newChunks = 1 - } - chunks := newChunks + m.tagStorer[tag] + 1 - m.tagStorer[tag] = chunks +const ( + isWinnerCall contractCall = iota + revealCall + commitCall + claimCall +) - m.storer[swarm.NewAddress(address).String()] = data - return address, nil +type mockContract struct { + callsList []contractCall + mtx sync.Mutex } -// DownloadBlob from swarm -func (m *BeeClient) DownloadBlob(address []byte) ([]byte, int, error) { - m.storerMu.Lock() - defer m.storerMu.Unlock() - if data, ok := m.storer[swarm.NewAddress(address).String()]; ok { - return data, http.StatusOK, nil - } - return nil, http.StatusInternalServerError, fmt.Errorf("error downloading data") +func (m *mockContract) Fee(ctx context.Context, txHash common.Hash) *big.Int { + return big.NewInt(1000) } -// UploadBzz downloads data to bzz api from the Swarm network. -func (*BeeClient) UploadBzz(_ []byte, _ string) ([]byte, error) { +func (m *mockContract) ReserveSalt(context.Context) ([]byte, error) { return nil, nil } -// DownloadBzz downloads bzz data from the Swarm network. -func (*BeeClient) DownloadBzz(_ []byte) ([]byte, int, error) { - return nil, 0, nil +func (m *mockContract) IsPlaying(context.Context, uint8) (bool, error) { + return true, nil } -// DeleteReference unpins chunk in swarm -func (m *BeeClient) DeleteReference(address []byte) error { - m.storerMu.Lock() - defer m.storerMu.Unlock() - if _, found := m.storer[swarm.NewAddress(address).String()]; found { - delete(m.storer, swarm.NewAddress(address).String()) - return nil - } - return errors.New("chunk not found") +func (m *mockContract) IsWinner(context.Context) (bool, error) { + m.mtx.Lock() + defer m.mtx.Unlock() + m.callsList = append(m.callsList, isWinnerCall) + return false, nil +} + +func (m *mockContract) Claim(context.Context, redistribution.ChunkInclusionProofs) (common.Hash, error) { + m.mtx.Lock() + defer m.mtx.Unlock() + m.callsList = append(m.callsList, claimCall) + return common.Hash{}, nil } -// CreateTag creates a tag -func (m *BeeClient) CreateTag(_ []byte) (uint32, error) { - tag := time.Now().UnixNano() - m.storerMu.Lock() - defer m.storerMu.Unlock() - m.tagStorer[uint32(tag)] = 0 - return uint32(tag), nil +func (m *mockContract) Commit(context.Context, []byte, uint64) (common.Hash, error) { + m.mtx.Lock() + defer m.mtx.Unlock() + m.callsList = append(m.callsList, commitCall) + return common.Hash{}, nil } -// GetTag returns the tag -func (m *BeeClient) GetTag(tag uint32) (int64, int64, int64, error) { - m.storerMu.Lock() - defer m.storerMu.Unlock() - return m.tagStorer[tag], m.tagStorer[tag], m.tagStorer[tag], nil +func (m *mockContract) Reveal(context.Context, uint8, []byte, []byte) (common.Hash, error) { + m.mtx.Lock() + defer m.mtx.Unlock() + m.callsList = append(m.callsList, revealCall) + return common.Hash{}, nil } + +type mockHealth struct{} + +func (m *mockHealth) IsHealthy() bool { return true } diff --git a/pkg/collection/batch_test.go b/pkg/collection/batch_test.go index c8e00ed9..981eb98e 100644 --- a/pkg/collection/batch_test.go +++ b/pkg/collection/batch_test.go @@ -21,6 +21,11 @@ import ( "io" "testing" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" @@ -32,15 +37,22 @@ import ( ) func TestBatchIndex(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) ai := acc.GetUserAccountInfo() _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) user := acc.GetAddress(account.UserAccountIndex) podPassword, _ := utils.GetRandString(pod.PasswordLength) t.Run("batch-add-docs", func(t *testing.T) { diff --git a/pkg/collection/document.go b/pkg/collection/document.go index 8cf1a02f..0bf83a00 100644 --- a/pkg/collection/document.go +++ b/pkg/collection/document.go @@ -21,10 +21,13 @@ import ( "bytes" "context" "encoding/binary" + "encoding/gob" + "encoding/hex" "encoding/json" "errors" "fmt" "io" + "math" "regexp" "strconv" "strings" @@ -68,6 +71,7 @@ type DocumentDB struct { simpleIndexes map[string]*Index mapIndexes map[string]*Index listIndexes map[string]*Index + vectorIndexes map[string]*Index } // DBSchema is the schema of a document DB @@ -77,6 +81,7 @@ type DBSchema struct { SimpleIndexes []SIndex `json:"simple_indexes,omitempty"` MapIndexes []SIndex `json:"map_indexes,omitempty"` ListIndexes []SIndex `json:"list_indexes,omitempty"` + VectorIndexes []SIndex `json:"vector_indexes,omitempty"` CompoundIndexes []CIndex `json:"compound_indexes,omitempty"` } @@ -146,6 +151,7 @@ func (d *Document) CreateDocumentDB(dbName, encryptionPassword string, indexes m var simpleIndexes []SIndex var mapIndexes []SIndex + var vectorIndexes []SIndex var listIndexes []SIndex // create the default index @@ -172,6 +178,9 @@ func (d *Document) CreateDocumentDB(dbName, encryptionPassword string, indexes m } else if fieldType == ListIndex { d.logger.Info("created list index: ", dbName, fieldName, fieldType, mutable) listIndexes = append(listIndexes, newIndex) + } else if fieldType == VectorIndex { + d.logger.Info("created vector index: ", dbName, fieldName, fieldType, mutable) + vectorIndexes = append(vectorIndexes, newIndex) } else { d.logger.Info("created simple index: ", dbName, fieldName, fieldType, mutable) simpleIndexes = append(simpleIndexes, newIndex) @@ -185,6 +194,7 @@ func (d *Document) CreateDocumentDB(dbName, encryptionPassword string, indexes m SimpleIndexes: simpleIndexes, MapIndexes: mapIndexes, ListIndexes: listIndexes, + VectorIndexes: vectorIndexes, } err = d.storeDocumentDBSchemas(encryptionPassword, docTables) @@ -216,7 +226,6 @@ func (d *Document) OpenDocumentDB(dbName, encryptionPassword string) error { d.logger.Errorf("opening document db: %v", ErrDocumentDBNotPresent) return ErrDocumentDBNotPresent } - // open the simple indexes simpleIndexs := make(map[string]*Index) for _, si := range schema.SimpleIndexes { @@ -253,6 +262,17 @@ func (d *Document) OpenDocumentDB(dbName, encryptionPassword string) error { listIndexes[li.FieldName] = idx } + // open the vector indexes + vectorIndexes := make(map[string]*Index) + for _, vi := range schema.VectorIndexes { + d.logger.Info("opening vector index: ", vi.FieldName) + idx, err := OpenIndex(d.podName, dbName, vi.FieldName, encryptionPassword, d.fd, d.ai, d.user, d.client, d.logger) + if err != nil { // skipcq: TCV-001 + d.logger.Errorf("opening vector index: %v", err.Error()) + return err + } + vectorIndexes[vi.FieldName] = idx + } // create the document DB index map docDB := &DocumentDB{ name: schema.Name, @@ -260,6 +280,7 @@ func (d *Document) OpenDocumentDB(dbName, encryptionPassword string) error { simpleIndexes: simpleIndexs, mapIndexes: mapIndexs, listIndexes: listIndexes, + vectorIndexes: vectorIndexes, } // add to the open DB map @@ -322,7 +343,15 @@ func (d *Document) DeleteDocumentDB(dbName, encryptionPassword string) error { d.logger.Info("deleting list index: ", li.name, li.indexType) err = li.DeleteIndex(encryptionPassword) if err != nil { // skipcq: TCV-001 - d.logger.Errorf("deleting map index: %v", err.Error()) + d.logger.Errorf("deleting map list: %v", err.Error()) + return err + } + } + for _, vi := range docDB.vectorIndexes { + d.logger.Info("deleting vector index: ", vi.name, vi.indexType) + err = vi.DeleteIndex(encryptionPassword) + if err != nil { // skipcq: TCV-001 + d.logger.Errorf("deleting vector index: %v", err.Error()) return err } } @@ -396,7 +425,14 @@ func (d *Document) DeleteAllDocumentDBs(encryptionPassword string) error { return err } } - + for _, vi := range docDB.vectorIndexes { + d.logger.Info("deleting vector index: ", vi.name, vi.indexType) + err = vi.DeleteIndex(encryptionPassword) + if err != nil { // skipcq: TCV-001 + d.logger.Errorf("deleting vector index: %v", err.Error()) + return err + } + } // delete the document db from the DB file delete(docTables, dbName) @@ -442,8 +478,11 @@ func (d *Document) Count(dbName, expr string) (uint64, error) { if !found { idx, found = db.listIndexes[fieldName] if !found { // skipcq: TCV-001 - d.logger.Errorf("counting document db: %v", ErrIndexNotPresent) - return 0, ErrIndexNotPresent + idx, found = db.vectorIndexes[fieldName] + if !found { // skipcq: TCV-001 + d.logger.Errorf("counting document db: %v", ErrIndexNotPresent) + return 0, ErrIndexNotPresent + } } } else { fieldValue = strings.ReplaceAll(fieldValue, ":", "") @@ -451,6 +490,9 @@ func (d *Document) Count(dbName, expr string) (uint64, error) { } switch idx.indexType { + case VectorIndex: + d.logger.Errorf("counting document db: vector index is not supported") + return 0, fmt.Errorf("vector index is not supported") case StringIndex: itr, err := idx.NewStringIterator(fieldValue, "", -1) if err != nil { // skipcq: TCV-001 @@ -651,9 +693,29 @@ func (d *Document) Put(dbName string, doc []byte) error { for field, index := range db.listIndexes { indexes[field] = index } + for field, index := range db.vectorIndexes { + indexes[field] = index + } for field, index := range indexes { v := docMap[field] // it is already checked to be present switch index.indexType { + case VectorIndex: + embedding := v.([]interface{}) + vector := make([]float32, 0, len(embedding)) + for _, vec := range embedding { + vector = append(vector, float32(vec.(float64))) + } + var buf bytes.Buffer + if err = gob.NewEncoder(&buf).Encode(vector); err != nil { + d.logger.Errorf("inserting in to document db: ", err.Error()) + return err + } + embeddingHex := hex.EncodeToString(buf.Bytes()) + err := index.Put(embeddingHex, ref, StringIndex, true) + if err != nil { // skipcq: TCV-001 + d.logger.Errorf("inserting in to document db: ", err.Error()) + return err + } case StringIndex: apnd := true if field == DefaultIndexFieldName { @@ -868,7 +930,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, d.logger.Info("finding from document db: ", dbName, expr, limit) db := d.getOpenedDb(dbName) if db == nil { // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", ErrDocumentDBNotOpened) + d.logger.Errorf("finding from document db: %v", ErrDocumentDBNotOpened) return nil, ErrDocumentDBNotOpened } @@ -876,7 +938,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, if expr == "" { idx, found := db.simpleIndexes[DefaultIndexFieldName] if !found { // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", ErrIndexNotPresent) + d.logger.Errorf("finding from document db: %v", ErrIndexNotPresent) return nil, ErrIndexNotPresent } return idx.Get("") @@ -884,7 +946,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, fieldName, operator, fieldValue, err := d.resolveExpression(expr) if err != nil { // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", err.Error()) + d.logger.Errorf("finding from document db: %v", err.Error()) return nil, err } @@ -894,7 +956,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, if !found { // skipcq: TCV-001 idx, found = db.listIndexes[fieldName] if !found { - d.logger.Errorf("finding from document db: ", ErrIndexNotPresent) + d.logger.Errorf("finding from document db: %v", ErrIndexNotPresent) return nil, ErrIndexNotPresent } } else { @@ -907,7 +969,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, case StringIndex: itr, err := idx.NewStringIterator(fieldValue, "", -1) if err != nil { // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", err.Error()) + d.logger.Errorf("finding from document db: %v", err.Error()) return nil, err } switch operator { @@ -942,7 +1004,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, case MapIndex, ListIndex: itr, err := idx.NewStringIterator(fieldValue, "", int64(limit)) if err != nil { // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", err.Error()) + d.logger.Errorf("finding from document db: %v", err.Error()) return nil, err } switch operator { @@ -976,7 +1038,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, if operator != "<" && operator != "<=" { start, err = strconv.ParseInt(fieldValue, 10, 64) if err != nil { // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", err.Error()) + d.logger.Errorf("finding from document db: %v", err.Error()) return nil, err } } else if operator == "!=" { @@ -984,7 +1046,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, } itr, err := idx.NewIntIterator(start, -1, int64(limit)) if err != nil { // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", err.Error()) + d.logger.Errorf("finding from document db: %v", err.Error()) return nil, err } switch operator { @@ -1022,7 +1084,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, case "<": end, err := strconv.ParseInt(fieldValue, 10, 64) if err != nil { // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", err.Error()) + d.logger.Errorf("finding from document db: %v", err.Error()) break } for itr.Next() { @@ -1042,7 +1104,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, case "<=": end, err := strconv.ParseInt(fieldValue, 10, 64) if err != nil { // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", err.Error()) + d.logger.Errorf("finding from document db: %v", err.Error()) break } for itr.Next() { @@ -1074,13 +1136,14 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, return nil, fmt.Errorf("operator is not available: %s", operator) } case BytesIndex: // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", ErrIndexNotSupported) + d.logger.Errorf("finding from document db: %v", ErrIndexNotSupported) return nil, ErrIndexNotSupported default: // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", ErrInvalidIndexType) + d.logger.Errorf("finding from document db: %v", ErrInvalidIndexType) return nil, ErrInvalidIndexType } var docs [][]byte + if idx.mutable { wg := new(sync.WaitGroup) mtx := &sync.Mutex{} @@ -1092,7 +1155,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, et := newEntryTask(d.client, &docs, ref, mtx) err := et.Execute(context.TODO()) if err != nil { // skipcq: TCV-001 - d.logger.Errorf("finding from document db: ", err.Error()) + d.logger.Errorf("finding from document db: %v", err.Error()) } wg.Done() } @@ -1112,7 +1175,7 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, } data, err := d.getLineFromFile(idx.podFile, podPassword, seekOffset) if err != nil { - d.logger.Errorf("finding from document db: ", err.Error()) + d.logger.Errorf("finding from document db: %v", err.Error()) return nil, err } docs = append(docs, data) @@ -1122,11 +1185,105 @@ func (d *Document) Find(dbName, expr, podPassword string, limit int) ([][]byte, } } +// NearestNodes does something TODO +func (d *Document) NearestNodes(dbName, podPassword, index string, v []float32, force float32, limit int) ([][]byte, error) { + d.logger.Info("finding distance from document db: ", dbName, v, limit) + db := d.getOpenedDb(dbName) + if db == nil { // skipcq: TCV-001 + d.logger.Errorf("finding distance from document db: ", ErrDocumentDBNotOpened) + return nil, ErrDocumentDBNotOpened + } + + if len(v) == 0 { + return nil, fmt.Errorf("vector is empty") + } + + idx, found := db.vectorIndexes[index] + if !found { + d.logger.Errorf("finding from document db: %v", ErrIndexNotPresent) + return nil, ErrIndexNotPresent + } + + itr, err := idx.NewStringIterator("", "", -1) + if err != nil { // skipcq: TCV-001 + d.logger.Errorf("finding from document db: %v", err.Error()) + return nil, err + } + + var references [][]byte + for itr.Next() { + if limit > 0 && references != nil && len(references) > limit { // skipcq: TCV-001 + break + } + vectorString := itr.StringKey() + vector := []float32{} + vectorBytes, err := hex.DecodeString(vectorString) + if err != nil { + continue + } + err = gob.NewDecoder(bytes.NewBuffer(vectorBytes)).Decode(&vector) + if err != nil { + d.logger.Errorf("finding from document db: %v", err.Error()) + return nil, err + } + dist, err := NormalizedDistance(v, vector) + if err != nil { + continue + } + + if dist < force { + refs := itr.ValueAll() + references = append(references, refs...) + } + } + + var docs [][]byte + if idx.mutable { + wg := new(sync.WaitGroup) + mtx := &sync.Mutex{} + for _, ref := range references { + if limit > 0 && len(docs) >= limit { + break + } + wg.Add(1) + et := newEntryTask(d.client, &docs, ref, mtx) + err := et.Execute(context.TODO()) + if err != nil { // skipcq: TCV-001 + d.logger.Errorf("finding from document db: %v", err.Error()) + } + wg.Done() + } + wg.Wait() + d.logger.Info("found document from document db: ", dbName, len(docs)) + return docs, nil + } else { // skipcq: TCV-001 + for _, ref := range references { + if limit > 0 && len(docs) >= limit { + break + } + b := bytes.NewBuffer(ref) + seekOffset, err := binary.ReadUvarint(b) + if err != nil { + d.logger.Errorf("getting from document db: ", err.Error()) + return nil, err + } + data, err := d.getLineFromFile(idx.podFile, podPassword, seekOffset) + if err != nil { + d.logger.Errorf("finding from document db: %v", err.Error()) + return nil, err + } + docs = append(docs, data) + } + d.logger.Info("found document from document db: ", dbName, len(docs)) + return docs, nil + } +} + // LoadDocumentDBSchemas loads the schema of all documents belonging to a pod. func (d *Document) LoadDocumentDBSchemas(encryptionPassword string) (map[string]DBSchema, error) { collections := make(map[string]DBSchema) topic := utils.HashString(documentFile) - _, data, err := d.fd.GetFeedData(topic, d.user, []byte(encryptionPassword)) + _, data, err := d.fd.GetFeedData(topic, d.user, []byte(encryptionPassword), false) if err != nil { if err.Error() != "feed does not exist or was not updated yet" { // skipcq: TCV-001 return collections, err @@ -1178,7 +1335,7 @@ func (d *Document) storeDocumentDBSchemas(encryptionPassword string, collections } } topic := utils.HashString(documentFile) - _, err := d.fd.UpdateFeed(d.user, topic, buf.Bytes(), []byte(encryptionPassword)) + err := d.fd.UpdateFeed(d.user, topic, buf.Bytes(), []byte(encryptionPassword), false) if err != nil { // skipcq: TCV-001 return err } @@ -1286,7 +1443,6 @@ func (d *Document) CreateDocBatch(dbName, podPassword string) (*DocBatch, error) docBatch.batches[fieldName] = batch d.logger.Info("created list batch index: ", fieldName) } - d.logger.Info("created batch for inserting in document db: ", dbName) return &docBatch, nil } @@ -1643,3 +1799,34 @@ func (et *entryTask) Execute(context.Context) error { func (et *entryTask) Name() string { return string(et.ref) } + +// NormalizedDistance between two arbitrary vectors, errors if dimensions don't +// match, will return results between 0 (no distance) and 1 (maximum distance) +func NormalizedDistance(a, b []float32) (float32, error) { + sim, err := cosineSim(a, b) + if err != nil { + return 1, fmt.Errorf("normalized distance: %v", err) + } + + return (1 - sim) / 2, nil +} + +func cosineSim(a, b []float32) (float32, error) { + if len(a) != len(b) { + return 0, fmt.Errorf("vectors have different dimensions") + } + + var ( + sumProduct float64 + sumASquare float64 + sumBSquare float64 + ) + + for i := range a { + sumProduct += float64(a[i] * b[i]) + sumASquare += float64(a[i] * a[i]) + sumBSquare += float64(b[i] * b[i]) + } + + return float32(sumProduct / (math.Sqrt(sumASquare) * math.Sqrt(sumBSquare))), nil +} diff --git a/pkg/collection/document_test.go b/pkg/collection/document_test.go index c3a05c93..b224be23 100644 --- a/pkg/collection/document_test.go +++ b/pkg/collection/document_test.go @@ -24,6 +24,11 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" + "github.com/fairdatasociety/fairOS-dfs/pkg/account" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/collection" @@ -44,18 +49,26 @@ type TestDocument struct { Age float64 `json:"age"` TagMap map[string]string `json:"tag_map"` TagList []string `json:"tag_list"` + Vector []float32 `json:"vector"` } func TestDocumentStore(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) ai := acc.GetUserAccountInfo() _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) user := acc.GetAddress(account.UserAccountIndex) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { @@ -66,7 +79,7 @@ func TestDocumentStore(t *testing.T) { docStore := collection.NewDocumentStore("pod1", fd, ai, user, file, tm, mockClient, logger) podPassword, _ := utils.GetRandString(pod.PasswordLength) t.Run("create_document_db_errors", func(t *testing.T) { - nilFd := feed.New(&account.Info{}, mockClient, logger) + nilFd := feed.New(&account.Info{}, mockClient, -1, 0, logger) nilDocStore := collection.NewDocumentStore("pod1", nilFd, ai, user, file, tm, mockClient, logger) err := nilDocStore.CreateDocumentDB("docdb_err", podPassword, nil, true) if !errors.Is(err, collection.ErrReadOnlyIndex) { @@ -623,7 +636,7 @@ func TestDocumentStore(t *testing.T) { tag1["tgf12"] = "tgv12" var list1 []string list1 = append(list1, "lst11", "lst12") - addDocument(t, docStore, "docdb_9", "1", "John", "Doe", 45, tag1, list1) + addDocument(t, docStore, "docdb_9", "1", "John", "Doe", 45, tag1, list1, nil) docs, err := docStore.Get("docdb_9", "1", podPassword) require.NoError(t, err) var gotDoc TestDocument @@ -663,7 +676,7 @@ func TestDocumentStore(t *testing.T) { tag1["tgf12"] = "tgv12" var list1 []string list1 = append(list1, "lst11", "lst12") - addDocument(t, docStore, "docdb_99", "1", "John", "Doe", 45, tag1, list1) + addDocument(t, docStore, "docdb_99", "1", "John", "Doe", 45, tag1, list1, nil) docs, err := docStore.Get("docdb_99", "1", podPassword) require.NoError(t, err) var gotDoc TestDocument @@ -700,8 +713,8 @@ func TestDocumentStore(t *testing.T) { tag1["tgf12"] = "tgv12" var list1 []string list1 = append(list1, "lst11", "lst12") - addDocument(t, docStore, "docdb_10", "1", "John", "Doe", 45, tag1, list1) - addDocument(t, docStore, "docdb_10", "1", "John", "Doe", 25, tag1, list1) + addDocument(t, docStore, "docdb_10", "1", "John", "Doe", 45, tag1, list1, nil) + addDocument(t, docStore, "docdb_10", "1", "John", "Doe", 25, tag1, list1, nil) // count the total docs using id field count1, err := docStore.Count("docdb_10", "") @@ -784,6 +797,30 @@ func TestDocumentStore(t *testing.T) { err = docStore.DeleteDocumentDB("docdb_11", podPassword) require.NoError(t, err) }) + + t.Run("vector_index", func(t *testing.T) { + // create a document DB + si := make(map[string]collection.IndexType) + si["first_name"] = collection.StringIndex + si["age"] = collection.NumberIndex + si["tag_map"] = collection.MapIndex + si["vector"] = collection.VectorIndex + si["tag_list"] = collection.ListIndex + createDocumentDBs(t, []string{"docdb_vector"}, docStore, si, podPassword) + + err := docStore.OpenDocumentDB("docdb_vector", podPassword) + require.NoError(t, err) + + // Add documents + createVectorDocuments(t, docStore, "docdb_vector") + + // Find documents + docs, err := docStore.NearestNodes("docdb_vector", podPassword, "vector", []float32{0.1, 0.1, 0.98}, .2, 0) + require.NoError(t, err) + + assert.Equal(t, 3, len(docs)) + }) + /* t.Run("batch-immutable", func(t *testing.T) { // create a document DB @@ -934,6 +971,52 @@ func checkIndex(t *testing.T, si collection.SIndex, filedName string, idxType co } } +func createVectorDocuments(t *testing.T, docStore *collection.Document, dbName string) { + t.Helper() + tag1 := make(map[string]string) + tag1["tgf11"] = "tgv11" + tag1["tgf12"] = "tgv12" + var list1 []string + list1 = append(list1, "lst11", "lst12") + vector1 := []float32{0.1, 0.1, 0.98} + addDocument(t, docStore, dbName, "1", "John", "Doe", 45.523793600000005, tag1, list1, vector1) + tag2 := make(map[string]string) + tag2["tgf21"] = "tgv21" + tag2["tgf22"] = "tgv22" + var list2 []string + list2 = append(list2, "lst21", "lst22") + vector2 := []float32{0.1, 0.1, 0.96} + addDocument(t, docStore, dbName, "2", "John", "boy", 25, tag2, list2, vector2) + tag3 := make(map[string]string) + tag3["tgf31"] = "tgv31" + tag3["tgf32"] = "tgv32" + var list3 []string + list3 = append(list3, "lst31", "lst32") + vector3 := []float32{0.1, 0.1, 0.93} + addDocument(t, docStore, dbName, "3", "Bob", "michel", 30, tag3, list3, vector3) + tag4 := make(map[string]string) + tag4["tgf41"] = "tgv41" + tag4["tgf42"] = "tgv42" + var list4 []string + list4 = append(list4, "lst41", "lst42") + vector4 := []float32{0.1, 0.98, 0.1} + addDocument(t, docStore, dbName, "4", "Charlie", "chaplin", 25, tag4, list4, vector4) + tag5 := make(map[string]string) + tag5["tgf51"] = "tgv51" + tag5["tgf52"] = "tgv52" + var list5 []string + list5 = append(list5, "lst51", "lst52") + vector5 := []float32{0.1, 0.93, 0.1} + addDocument(t, docStore, dbName, "5", "Alice", "wonderland", 25, tag5, list5, vector5) + tag6 := make(map[string]string) + tag6["tgf61"] = "tgv61" + tag6["tgf62"] = "tgv62" + var list6 []string + list6 = append(list6, "lst61", "lst62") + vector6 := []float32{0.1, 0.92, 0.1} + addDocument(t, docStore, dbName, "6", "Zuri", "wonder", 52, tag6, list6, vector6) +} + func createTestDocuments(t *testing.T, docStore *collection.Document, dbName string) { t.Helper() tag1 := make(map[string]string) @@ -941,40 +1024,40 @@ func createTestDocuments(t *testing.T, docStore *collection.Document, dbName str tag1["tgf12"] = "tgv12" var list1 []string list1 = append(list1, "lst11", "lst12") - addDocument(t, docStore, dbName, "1", "John", "Doe", 45.523793600000005, tag1, list1) + addDocument(t, docStore, dbName, "1", "John", "Doe", 45.523793600000005, tag1, list1, nil) tag2 := make(map[string]string) tag2["tgf21"] = "tgv21" tag2["tgf22"] = "tgv22" var list2 []string list2 = append(list2, "lst21", "lst22") - addDocument(t, docStore, dbName, "2", "John", "boy", 25, tag2, list2) + addDocument(t, docStore, dbName, "2", "John", "boy", 25, tag2, list2, nil) tag3 := make(map[string]string) tag3["tgf31"] = "tgv31" tag3["tgf32"] = "tgv32" var list3 []string list3 = append(list3, "lst31", "lst32") - addDocument(t, docStore, dbName, "3", "Bob", "michel", 30, tag3, list3) + addDocument(t, docStore, dbName, "3", "Bob", "michel", 30, tag3, list3, nil) tag4 := make(map[string]string) tag4["tgf41"] = "tgv41" tag4["tgf42"] = "tgv42" var list4 []string list4 = append(list4, "lst41", "lst42") - addDocument(t, docStore, dbName, "4", "Charlie", "chaplin", 25, tag4, list4) + addDocument(t, docStore, dbName, "4", "Charlie", "chaplin", 25, tag4, list4, nil) tag5 := make(map[string]string) tag5["tgf51"] = "tgv51" tag5["tgf52"] = "tgv52" var list5 []string list5 = append(list5, "lst51", "lst52") - addDocument(t, docStore, dbName, "5", "Alice", "wonderland", 25, tag5, list5) + addDocument(t, docStore, dbName, "5", "Alice", "wonderland", 25, tag5, list5, nil) tag6 := make(map[string]string) tag6["tgf61"] = "tgv61" tag6["tgf62"] = "tgv62" var list6 []string list6 = append(list6, "lst61", "lst62") - addDocument(t, docStore, dbName, "6", "Zuri", "wonder", 52, tag6, list6) + addDocument(t, docStore, dbName, "6", "Zuri", "wonder", 52, tag6, list6, nil) } -func addDocument(t *testing.T, docStore *collection.Document, dbName, id, fname, lname string, age float64, tagMap map[string]string, tagList []string) { +func addDocument(t *testing.T, docStore *collection.Document, dbName, id, fname, lname string, age float64, tagMap map[string]string, tagList []string, vector []float32) { t.Helper() // create the doc doc := &TestDocument{ @@ -984,6 +1067,7 @@ func addDocument(t *testing.T, docStore *collection.Document, dbName, id, fname, Age: age, TagMap: tagMap, TagList: tagList, + Vector: vector, } // marshall the doc diff --git a/pkg/collection/entry.go b/pkg/collection/entry.go index 13af4d62..c942dd5d 100644 --- a/pkg/collection/entry.go +++ b/pkg/collection/entry.go @@ -24,6 +24,7 @@ type Manifest struct { IdxType IndexType `json:"index_type"` CreationTime int64 `json:"creation_time"` Entries []*Entry `json:"entries,omitempty"` + Count uint64 `json:"count,omitempty"` // number of entries in the kv table, this should be updated on root manifest dirtyFlag bool } diff --git a/pkg/collection/index.go b/pkg/collection/index.go index 938e5b33..cef4a848 100644 --- a/pkg/collection/index.go +++ b/pkg/collection/index.go @@ -19,16 +19,17 @@ package collection import ( "context" "encoding/json" + "errors" "fmt" "net/http" "runtime" - "strings" "sync/atomic" "time" "github.com/fairdatasociety/fairOS-dfs/pkg/account" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" ) @@ -49,6 +50,8 @@ const ( MapIndex // ListIndex is returned when the index type is list ListIndex + + VectorIndex ) func (e IndexType) String() string { @@ -63,6 +66,8 @@ func (e IndexType) String() string { return "MapIndex" case ListIndex: return "ListIndex" + case VectorIndex: // skipcq: TCV-001 + return "VectorIndex" default: return "InvalidIndex" } @@ -80,6 +85,8 @@ func toIndexTypeEnum(s string) IndexType { return MapIndex case "ListIndex": return ListIndex + case "VectorIndex": // skipcq: TCV-001 + return VectorIndex default: return InvalidIndex } @@ -113,7 +120,7 @@ func CreateIndex(podName, collectionName, indexName, encryptionPassword string, } actualIndexName := podName + collectionName + indexName topic := utils.HashString(actualIndexName) - _, oldData, err := fd.GetFeedData(topic, user, []byte(encryptionPassword)) + _, oldData, err := fd.GetFeedData(topic, user, []byte(encryptionPassword), false) if err == nil && len(oldData) != 0 && string(oldData) != utils.DeletedFeedMagicWord { // if the feed is present, and it has some data means there index is still valid return ErrIndexAlreadyPresent @@ -133,13 +140,13 @@ func CreateIndex(podName, collectionName, indexName, encryptionPassword string, } if string(oldData) == utils.DeletedFeedMagicWord { // skipcq: TCV-001 - _, err = fd.UpdateFeed(user, topic, ref, []byte(encryptionPassword)) + err = fd.UpdateFeed(user, topic, ref, []byte(encryptionPassword), false) if err != nil { return ErrManifestCreate } return nil } - _, err = fd.CreateFeed(user, topic, ref, []byte(encryptionPassword)) + err = fd.CreateFeed(user, topic, ref, []byte(encryptionPassword)) if err != nil { // skipcq: TCV-001 return ErrManifestCreate } @@ -153,7 +160,6 @@ func OpenIndex(podName, collectionName, indexName, podPassword string, fd *feed. if manifest == nil { return nil, ErrIndexNotPresent } - idx := &Index{ name: manifest.Name, encryptionPassword: podPassword, @@ -164,7 +170,7 @@ func OpenIndex(podName, collectionName, indexName, podPassword string, fd *feed. accountInfo: ai, feed: fd, client: client, - count: 0, + count: manifest.Count, memDB: manifest, logger: logger, } @@ -183,13 +189,25 @@ func (idx *Index) DeleteIndex(encryptionPassword string) error { // erase the top Manifest topic := utils.HashString(idx.name) - _, err := idx.feed.UpdateFeed(idx.user, topic, []byte(utils.DeletedFeedMagicWord), []byte(encryptionPassword)) + err := idx.feed.UpdateFeed(idx.user, topic, []byte(utils.DeletedFeedMagicWord), []byte(encryptionPassword), false) if err != nil { // skipcq: TCV-001 return ErrDeleteingIndex } return nil } +func (idx *Index) IsEmpty(encryptionPassword string) (bool, error) { + if idx.memDB == nil || idx.memDB.Entries == nil { + manifest, err := idx.loadManifest(idx.name, encryptionPassword) + if err != nil { + return true, err + } + idx.memDB = manifest + } + + return len(idx.memDB.Entries) == 0, nil +} + // CountIndex counts the entries in an index. func (idx *Index) CountIndex(encryptionPassword string) (uint64, error) { if idx.memDB == nil || idx.memDB.Entries == nil { @@ -253,7 +271,7 @@ func (idx *Index) loadManifest(manifestPath, encryptionPassword string) (*Manife // get feed data and unmarshall the Manifest idx.logger.Info("loading Manifest: ", manifestPath) topic := utils.HashString(manifestPath) - _, refData, err := idx.feed.GetFeedData(topic, idx.user, []byte(encryptionPassword)) + _, refData, err := idx.feed.GetFeedData(topic, idx.user, []byte(encryptionPassword), false) if err != nil { // skipcq: TCV-001 return nil, ErrNoManifestFound } @@ -288,7 +306,7 @@ func (idx *Index) updateManifest(manifest *Manifest, encryptionPassword string) } topic := utils.HashString(manifest.Name) - _, err = idx.feed.UpdateFeed(idx.user, topic, ref, []byte(encryptionPassword)) + err = idx.feed.UpdateFeed(idx.user, topic, ref, []byte(encryptionPassword), false) if err != nil { // skipcq: TCV-001 return ErrManifestCreate } @@ -312,16 +330,19 @@ func (idx *Index) storeManifest(manifest *Manifest, encryptionPassword string) e return ErrManifestCreate } topic := utils.HashString(manifest.Name) - _, err = idx.feed.CreateFeed(idx.user, topic, ref, []byte(encryptionPassword)) - if err != nil { // skipcq: TCV-001 - if strings.Contains(err.Error(), "chunk already exists") { - _, err = idx.feed.UpdateFeed(idx.user, topic, ref, []byte(encryptionPassword)) - if err != nil { // skipcq: TCV-001 - return ErrManifestCreate - } - } else { + _, _, err = idx.feed.GetFeedData(topic, idx.user, []byte(encryptionPassword), false) + if err == nil || errors.Is(err, file.ErrDeletedFeed) { + err = idx.feed.UpdateFeed(idx.user, topic, ref, []byte(encryptionPassword), false) + if err != nil { // skipcq: TCV-001 + idx.logger.Errorf("updateFeed failed in storeManifest : %s", err.Error()) return ErrManifestCreate } + return nil + } + err = idx.feed.CreateFeed(idx.user, topic, ref, []byte(encryptionPassword)) + if err != nil { // skipcq: TCV-001 + idx.logger.Errorf("createFeed failed in storeManifest : %s", err.Error()) + return ErrManifestCreate } return nil } @@ -355,7 +376,7 @@ func longestCommonPrefix(str1, str2 string) (string, string, string) { func getRootManifestOfIndex(actualIndexName, encryptionPassword string, fd *feed.API, user utils.Address, client blockstore.Client) *Manifest { var manifest Manifest topic := utils.HashString(actualIndexName) - _, addr, err := fd.GetFeedData(topic, user, []byte(encryptionPassword)) + _, addr, err := fd.GetFeedData(topic, user, []byte(encryptionPassword), false) if err != nil { return nil } diff --git a/pkg/collection/index_api.go b/pkg/collection/index_api.go index cd5c7b9e..9fdb7e2b 100644 --- a/pkg/collection/index_api.go +++ b/pkg/collection/index_api.go @@ -23,6 +23,7 @@ import ( "fmt" "path/filepath" "strings" + "sync/atomic" "time" ) @@ -120,6 +121,9 @@ func (idx *Index) Delete(key string) ([][]byte, error) { } manifest.Entries = append(manifest.Entries[:i], manifest.Entries[i+1:]...) + var delta int = -1 + atomic.AddUint64(&idx.count, uint64(delta)) + manifest.Count = idx.count err = idx.updateManifest(manifest, idx.encryptionPassword) if err != nil { return nil, err @@ -315,6 +319,10 @@ func (idx *Index) addOrUpdateStringEntry(ctx context.Context, manifest *Manifest } if entryAdded && !memory { + // update the count + atomic.AddUint64(&idx.count, 1) + manifest.Count = idx.count + return idx.updateManifest(manifest, idx.encryptionPassword) } return nil // skipcq: TCV-001 @@ -385,7 +393,7 @@ func (idx *Index) seekManifestAndEntry(key string) (*Manifest, *Manifest, int, e } // if there are any elements in the index, then search for the entry - if fm.Entries != nil && len(fm.Entries) > 0 { + if fm != nil && fm.Entries != nil && len(fm.Entries) > 0 { return idx.findManifest(nil, fm, key) } return nil, nil, 0, ErrEntryNotFound diff --git a/pkg/collection/index_api_test.go b/pkg/collection/index_api_test.go index 071c3f3e..4af8bfa3 100644 --- a/pkg/collection/index_api_test.go +++ b/pkg/collection/index_api_test.go @@ -23,6 +23,11 @@ import ( "net/http" "testing" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" @@ -34,15 +39,22 @@ import ( ) func TestIndexAPI(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) ai := acc.GetUserAccountInfo() _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) user := acc.GetAddress(account.UserAccountIndex) podPassword, _ := utils.GetRandString(pod.PasswordLength) t.Run("get-doc", func(t *testing.T) { @@ -140,7 +152,7 @@ func TestIndexAPI(t *testing.T) { } -func addDoc(t *testing.T, key string, value []byte, index *collection.Index, client *mock.BeeClient, apnd bool) { +func addDoc(t *testing.T, key string, value []byte, index *collection.Index, client *bee.Client, apnd bool) { ref, err := client.UploadBlob(value, 0, false) if err != nil { t.Fatalf("could not add doc %s:%s, %v", key, value, err) @@ -151,7 +163,7 @@ func addDoc(t *testing.T, key string, value []byte, index *collection.Index, cli } } -func getDoc(t *testing.T, key string, index *collection.Index, client *mock.BeeClient) []byte { +func getDoc(t *testing.T, key string, index *collection.Index, client *bee.Client) []byte { ref, err := index.Get(key) if err != nil { if errors.Is(err, collection.ErrEntryNotFound) { @@ -168,7 +180,7 @@ func getDoc(t *testing.T, key string, index *collection.Index, client *mock.BeeC } return data } -func getAllDocs(t *testing.T, key string, index *collection.Index, client *mock.BeeClient) [][]byte { +func getAllDocs(t *testing.T, key string, index *collection.Index, client *bee.Client) [][]byte { refs, err := index.Get(key) if err != nil { if errors.Is(err, collection.ErrEntryNotFound) { @@ -191,7 +203,7 @@ func getAllDocs(t *testing.T, key string, index *collection.Index, client *mock. return data } -func getValue(t *testing.T, ref []byte, client *mock.BeeClient) []byte { +func getValue(t *testing.T, ref []byte, client *bee.Client) []byte { data, respCode, err := client.DownloadBlob(ref) if err != nil { t.Fatal(err) @@ -202,7 +214,7 @@ func getValue(t *testing.T, ref []byte, client *mock.BeeClient) []byte { return data } -func delDoc(t *testing.T, key string, index *collection.Index, client *mock.BeeClient) []byte { +func delDoc(t *testing.T, key string, index *collection.Index, client *bee.Client) []byte { ref, err := index.Delete(key) if err != nil { t.Fatal(err) @@ -218,7 +230,7 @@ func delDoc(t *testing.T, key string, index *collection.Index, client *mock.BeeC } -func addLotOfDocs(t *testing.T, index *collection.Index, client *mock.BeeClient) map[string][]byte { +func addLotOfDocs(t *testing.T, index *collection.Index, client *bee.Client) map[string][]byte { // Initialize the values kvMap := make(map[string][]byte) kvMap["key1"] = []byte("value1") @@ -252,7 +264,7 @@ func addLotOfDocs(t *testing.T, index *collection.Index, client *mock.BeeClient) return kvMap } -func addBatchDocs(t *testing.T, batch *collection.Batch, client *mock.BeeClient) map[string][]byte { +func addBatchDocs(t *testing.T, batch *collection.Batch, client *bee.Client) map[string][]byte { kvMap := make(map[string][]byte) kvMap["key1"] = []byte("value1") kvMap["key11"] = []byte("value11") diff --git a/pkg/collection/index_test.go b/pkg/collection/index_test.go index 71cef3d7..b7ced4bd 100644 --- a/pkg/collection/index_test.go +++ b/pkg/collection/index_test.go @@ -24,6 +24,11 @@ import ( "strconv" "testing" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/account" @@ -36,15 +41,22 @@ import ( ) func TestIndex(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) ai := acc.GetUserAccountInfo() _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) user := acc.GetAddress(account.UserAccountIndex) podPassword, _ := utils.GetRandString(pod.PasswordLength) t.Run("create_index", func(t *testing.T) { @@ -198,7 +210,7 @@ func TestIndex(t *testing.T) { func isIndexPresent(t *testing.T, podName, collectionName, indexName, encryptionPassword string, fd *feed.API, user utils.Address, client blockstore.Client) bool { actualIndexName := podName + collectionName + indexName topic := utils.HashString(actualIndexName) - _, addr, err := fd.GetFeedData(topic, user, []byte(encryptionPassword)) + _, addr, err := fd.GetFeedData(topic, user, []byte(encryptionPassword), false) if err == nil && len(addr) != 0 { data, _, err := client.DownloadBlob(addr) if err != nil { diff --git a/pkg/collection/iterator_test.go b/pkg/collection/iterator_test.go index 251866f7..41c42b0c 100644 --- a/pkg/collection/iterator_test.go +++ b/pkg/collection/iterator_test.go @@ -25,6 +25,11 @@ import ( "strconv" "testing" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/account" @@ -37,15 +42,22 @@ import ( ) func TestIndexIterator(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) ai := acc.GetUserAccountInfo() _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) user := acc.GetAddress(account.UserAccountIndex) podPassword, _ := utils.GetRandString(pod.PasswordLength) t.Run("iterate_all_string_keys", func(t *testing.T) { diff --git a/pkg/collection/kv.go b/pkg/collection/kv.go index 22badf52..5fc92ea1 100644 --- a/pkg/collection/kv.go +++ b/pkg/collection/kv.go @@ -141,6 +141,10 @@ func (kv *KeyValue) DeleteKVTable(name, encryptionPassword string) error { return kv.storeKVTables(kvtables, encryptionPassword) } +func (kv *KeyValue) Commit() { + kv.fd.CommitFeeds() +} + // DeleteAllKVTables deletes all key value tables with all their index and data entries. func (kv *KeyValue) DeleteAllKVTables(encryptionPassword string) error { if kv.fd.IsReadOnlyFeed() { // skipcq: TCV-001 @@ -219,32 +223,26 @@ func (kv *KeyValue) OpenKVTable(name, encryptionPassword string) error { } // KVCount counts the number of entries in the given key value table. -func (kv *KeyValue) KVCount(name, encryptionPassword string) (*TableKeyCount, error) { +func (kv *KeyValue) KVCount(name string) (*TableKeyCount, error) { kv.openKVTMu.Lock() defer kv.openKVTMu.Unlock() if table, ok := kv.openKVTables[name]; ok { - count, err := table.index.CountIndex(table.index.encryptionPassword) - if err != nil { - return nil, err - } return &TableKeyCount{ - Count: count, - TableName: name, - }, nil - } else { - idx, err := OpenIndex(kv.podName, defaultCollectionName, name, encryptionPassword, kv.fd, kv.ai, kv.user, kv.client, kv.logger) - if err != nil { - return nil, err - } - count, err := idx.CountIndex(idx.encryptionPassword) - if err != nil { - return nil, err - } - return &TableKeyCount{ - Count: count, + Count: table.index.count, TableName: name, }, nil } + return nil, ErrKVTableNotOpened +} + +// IsEmpty checks if the given key value table is empty. +func (kv *KeyValue) IsEmpty(name string) (bool, error) { + kv.openKVTMu.Lock() + defer kv.openKVTMu.Unlock() + if table, ok := kv.openKVTables[name]; ok { + return table.index.IsEmpty(table.index.encryptionPassword) + } + return false, ErrKVTableNotOpened } // KVPut inserts a given key and value in to the KV table. @@ -254,8 +252,9 @@ func (kv *KeyValue) KVPut(name, key string, value []byte) error { } kv.openKVTMu.Lock() - defer kv.openKVTMu.Unlock() - if table, ok := kv.openKVTables[name]; ok { + table, ok := kv.openKVTables[name] + kv.openKVTMu.Unlock() + if ok { switch table.indexType { case StringIndex: return table.index.Put(key, value, StringIndex, false) @@ -281,8 +280,9 @@ func (kv *KeyValue) KVPut(name, key string, value []byte) error { // KVGet retrieves a value from the KV table given a key. func (kv *KeyValue) KVGet(name, key string) ([]string, []byte, error) { kv.openKVTMu.Lock() - defer kv.openKVTMu.Unlock() - if table, ok := kv.openKVTables[name]; ok { + table, ok := kv.openKVTables[name] + kv.openKVTMu.Unlock() + if ok { value, err := table.index.Get(key) if err != nil { return nil, nil, err @@ -415,7 +415,7 @@ func (kv *KeyValue) KVGetNext(name string) ([]string, string, []byte, error) { func (kv *KeyValue) LoadKVTables(encryptionPassword string) (map[string][]string, error) { collections := make(map[string][]string) topic := utils.HashString(kvFile) - _, data, err := kv.fd.GetFeedData(topic, kv.user, []byte(encryptionPassword)) + _, data, err := kv.fd.GetFeedData(topic, kv.user, []byte(encryptionPassword), false) if err != nil { if err.Error() != "feed does not exist or was not updated yet" { // skipcq: TCV-001 return collections, err @@ -454,7 +454,7 @@ func (kv *KeyValue) storeKVTables(collections map[string][]string, encryptionPas if buf.Len() == 0 { data = []byte(utils.DeletedFeedMagicWord) } - _, err := kv.fd.UpdateFeed(kv.user, topic, data, []byte(encryptionPassword)) + err := kv.fd.UpdateFeed(kv.user, topic, data, []byte(encryptionPassword), false) if err != nil { // skipcq: TCV-001 return err } diff --git a/pkg/collection/kv_test.go b/pkg/collection/kv_test.go index 42d24aa2..f5b773cf 100644 --- a/pkg/collection/kv_test.go +++ b/pkg/collection/kv_test.go @@ -27,6 +27,12 @@ import ( "strconv" "strings" "testing" + "time" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" "github.com/fairdatasociety/fairOS-dfs/pkg/account" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" @@ -43,19 +49,28 @@ func TestMain(m *testing.M) { } func TestKeyValueStore(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + logger := logging.New(io.Discard, logrus.DebugLevel) + acc := account.New(logger) ai := acc.GetUserAccountInfo() _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) - user := acc.GetAddress(account.UserAccountIndex) - kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) - podPassword, _ := utils.GetRandString(pod.PasswordLength) + t.Run("table_not_opened", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_1314", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -82,6 +97,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("nil_itr", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_1312", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -104,6 +131,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("create_kv_table_with_string_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_0", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -131,6 +170,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("create_kv_table_with_number_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_1", podPassword, collection.NumberIndex) if err != nil { t.Fatal(err) @@ -158,6 +209,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("check_delete", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_2", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -180,6 +243,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("create_multiple_kv_tables_and_delete", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_31", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -260,6 +335,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("create_open_and_delete", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_4", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -280,6 +367,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("delete_without_create", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) // delete the last table err = kvStore.DeleteKVTable("kv_table_5", podPassword) if !errors.Is(err, collection.ErrKVTableNotPresent) { @@ -288,6 +387,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("open_table", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_6", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -305,6 +416,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("open_without_create", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err = kvStore.OpenKVTable("kv_table_7", podPassword) if !errors.Is(err, collection.ErrKVTableNotPresent) { t.Fatal("was able to open table without creating it") @@ -312,6 +435,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("put_string_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_8", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -337,7 +472,7 @@ func TestKeyValueStore(t *testing.T) { t.Fatal(err) } - countObject, err := kvStore.KVCount("kv_table_8", podPassword) + countObject, err := kvStore.KVCount("kv_table_8") if err != nil { t.Fatal(err) } @@ -347,6 +482,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("put_bytes_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_bytes", podPassword, collection.BytesIndex) if err != nil { t.Fatal(err) @@ -376,7 +523,7 @@ func TestKeyValueStore(t *testing.T) { t.Fatal(err) } - countObject, err := kvStore.KVCount("kv_table_bytes", podPassword) + countObject, err := kvStore.KVCount("kv_table_bytes") if err != nil { t.Fatal(err) } @@ -386,6 +533,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("put_chinese_string_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_9", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -413,6 +572,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("put_string_in_number_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_10", podPassword, collection.NumberIndex) if err != nil { t.Fatal(err) @@ -428,6 +599,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("put_get_del_get_string_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_11", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -457,6 +640,15 @@ func TestKeyValueStore(t *testing.T) { t.Fatal("values do not match", string(value), "value1") } + // test count + countObject, err := kvStore.KVCount("kv_table_11") + if err != nil { + t.Fatal(err) + } + + if countObject.Count != 2 { + t.Fatal("kv count value should be two") + } // delete the key _, err = kvStore.KVDelete("kv_table_11", "key1") if err != nil { @@ -472,6 +664,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("put_without_opening_table", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_12", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -483,6 +687,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("delete_non_existent_string_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_13", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -504,6 +720,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("batch_without_open", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_batch_1", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -516,6 +744,18 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("batch_columns_and_get_values", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_batch_2", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -558,9 +798,30 @@ func TestKeyValueStore(t *testing.T) { if !bytes.Equal(value, gotValue) { t.Fatal("values do not match", string(value), string(gotValue)) } + + // check the count + countObject, err := kvStore.KVCount("kv_table_batch_2") + if err != nil { + t.Fatal(err) + } + if countObject.Count != 1 { + t.Fatal("kv count value should be one") + } }) t.Run("batch_put_columns_and_get_values", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + defer fd.CommitFeeds() + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_batch_9", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -611,11 +872,30 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("count_columns_and_get_values", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_batch_count", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) } - countObject, err := kvStore.KVCount("kv_table_batch_count", podPassword) + _, err = kvStore.KVCount("kv_table_batch_count") + if !errors.Is(err, collection.ErrKVTableNotOpened) { + t.Fatal("should have returned error ", collection.ErrKVTableNotOpened) + } + err = kvStore.OpenKVTable("kv_table_batch_count", podPassword) + if err != nil { + t.Fatal(err) + } + countObject, err := kvStore.KVCount("kv_table_batch_count") if err != nil { t.Fatal(err) } @@ -625,6 +905,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("Iterate_string_keys", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, 500, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_Itr_0", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -638,6 +929,15 @@ func TestKeyValueStore(t *testing.T) { if err != nil { t.Fatal(err) } + fd.CommitFeeds() + // check the count + countObject, err := kvStore.KVCount("kv_table_Itr_0") + if err != nil { + t.Fatal(err) + } + if countObject.Count != 100 { + t.Fatal("kv count value should be 100") + } sortedKeys, sortedValues := sortLexicographically(t, keys, values) itr, err := kvStore.KVSeek("kv_table_Itr_0", "", "", -1) @@ -649,7 +949,7 @@ func TestKeyValueStore(t *testing.T) { for i := 0; i < 100; i++ { itr.Next() if itr.StringKey() != sortedKeys[i] { - t.Fatal("keys do not match", itr.StringKey(), sortedKeys[i]) + t.Fatal("keys do not match", i, itr.StringKey(), sortedKeys[i]) } if !bytes.Equal(itr.Value(), []byte(sortedValues[i])) { t.Fatal("values do not match", string(itr.Value()), sortedValues[i]) @@ -658,6 +958,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("Iterate_seek_limit_string_keys", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) tableNo := 0 research: tableNo++ @@ -696,7 +1007,7 @@ func TestKeyValueStore(t *testing.T) { for i := startIndex; i < startIndex+10; i++ { itr.Next() if itr.StringKey() != sortedKeys[i] { - t.Fatalf("key mismatch: %s : %s\n", itr.StringKey(), sortedKeys[i]) + t.Fatalf("key mismatch: %s : %s at %d\n", itr.StringKey(), sortedKeys[i], i) } if !bytes.Equal(itr.Value(), []byte(sortedValues[i])) { t.Fatalf("value mismatch: %s : %s\n", itr.StringKey(), sortedKeys[i]) @@ -711,6 +1022,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("Iterate_seek_start_end_string_keys", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) tableNo := 0 research: tableNo++ @@ -781,6 +1103,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("Iterate_seek_start_end_string_keys_over_a_known_failing_keys", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) tableNo := 486 err := kvStore.CreateKVTable(fmt.Sprintf("kv_table_Itr_1%d", tableNo), podPassword, collection.StringIndex) if err != nil { @@ -816,6 +1149,14 @@ func TestKeyValueStore(t *testing.T) { } } + // check the count + count, err := kvStore.KVCount(fmt.Sprintf("kv_table_Itr_1%d", tableNo)) + if err != nil { + t.Fatal(err) + } + if count.Count != uint64(len(list)) { + t.Fatal("count mismatch", count, len(list)) + } startIndex := 0 endIndex := 0 @@ -859,6 +1200,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("Iterate_string_of_numbers_keys", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_Itr_3", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -892,6 +1244,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("Iterate_numbers_keys", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_Itr_4", podPassword, collection.NumberIndex) if err != nil { t.Fatal(err) @@ -926,6 +1289,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("Iterate_numbers_start_end_keys", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_Itr_5", podPassword, collection.NumberIndex) if err != nil { t.Fatal(err) @@ -979,6 +1353,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("Iterate_numbers_start_and_limit_keys", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_Itr_6", podPassword, collection.NumberIndex) if err != nil { t.Fatal(err) @@ -1026,6 +1411,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("get_non_existent_string_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_1313", podPassword, collection.StringIndex) if err != nil { t.Fatal(err) @@ -1056,6 +1452,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("err_byte_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_1316", podPassword, collection.BytesIndex) if err != nil { t.Fatal(err) @@ -1076,6 +1483,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("err_seek_list_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_1317", podPassword, collection.ListIndex) if err != nil { t.Fatal(err) @@ -1092,6 +1510,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("err_seek_map_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_1318", podPassword, collection.MapIndex) if err != nil { t.Fatal(err) @@ -1108,6 +1537,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("err_seek_invalid_index", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_1319", podPassword, collection.InvalidIndex) if err != nil { t.Fatal(err) @@ -1124,6 +1564,17 @@ func TestKeyValueStore(t *testing.T) { }) t.Run("seek_unopened_table", func(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + user := acc.GetAddress(account.UserAccountIndex) + kvStore := collection.NewKeyValueStore("pod1", fd, ai, user, mockClient, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) err := kvStore.CreateKVTable("kv_table_1320", podPassword, collection.ListIndex) if err != nil { t.Fatal(err) @@ -1136,7 +1587,7 @@ func TestKeyValueStore(t *testing.T) { }) } -func addRandomStrings(t *testing.T, kvStore *collection.KeyValue, count int, tableName string) ([]string, []string, error) { +func addRandomStrings(_ *testing.T, kvStore *collection.KeyValue, count int, tableName string) ([]string, []string, error) { var keys []string var values []string for i := 0; i < count; i++ { @@ -1162,11 +1613,12 @@ func addRandomStrings(t *testing.T, kvStore *collection.KeyValue, count int, tab } keys = append(keys, key) values = append(values, key) + <-time.After(800 * time.Millisecond) } return keys, values, nil } -func addRandomNumbersAsString(t *testing.T, kvStore *collection.KeyValue, count int, tableName string) ([]string, []string, error) { +func addRandomNumbersAsString(_ *testing.T, kvStore *collection.KeyValue, count int, tableName string) ([]string, []string, error) { var keys []string var values []string for i := 0; i < count; i++ { @@ -1193,7 +1645,7 @@ func addRandomNumbersAsString(t *testing.T, kvStore *collection.KeyValue, count return keys, values, nil } -func addRandomNumbers(t *testing.T, kvStore *collection.KeyValue, count int, tableName string) ([]int, []int, error) { +func addRandomNumbers(_ *testing.T, kvStore *collection.KeyValue, count int, tableName string) ([]int, []int, error) { var keys []int var values []int for i := 0; i < count; i++ { diff --git a/pkg/contracts/config.go b/pkg/contracts/config.go index 3f527128..d30ae641 100644 --- a/pkg/contracts/config.go +++ b/pkg/contracts/config.go @@ -34,7 +34,7 @@ func TestnetConfig(chainId string) (*ENSConfig, *SubscriptionConfig) { e.FDSRegistrarAddress = "0xFBF00389140C00384d88d458239833E3231a7414" e.PublicResolverAddress = "0xC904989B579c2B216A75723688C784038AA99B56" - s.DataHubAddress = "0xbF38b92a9baE1e23e150A66c7A44412828210371" + s.DataHubAddress = "0xBE41b272e3cDe3aeC8fE4a144C5b7cE71D9e6498" case Goerli: e.ENSRegistryAddress = "0x42B22483e3c8dF794f351939620572d1a3193c12" e.FDSRegistrarAddress = "0xF4C9Cd25031E3BB8c5618299bf35b349c1aAb6A9" diff --git a/pkg/contracts/datahub/Datahub.go b/pkg/contracts/datahub/Datahub.go index f589a76c..31775dd1 100644 --- a/pkg/contracts/datahub/Datahub.go +++ b/pkg/contracts/datahub/Datahub.go @@ -26,6 +26,7 @@ var ( _ = common.Big1 _ = types.BloomLookup _ = event.NewSubscription + _ = abi.ConvertType ) // DataHubActiveBid is an auto generated low-level Go binding around an user-defined struct. @@ -49,10 +50,11 @@ type DataHubSub struct { Price *big.Int Active bool Earned *big.Int - Bids uint32 - Sells uint32 - Reports uint32 - DaysValid uint16 + Category [32]byte + Bids *big.Int + Sells *big.Int + Reports *big.Int + DaysValid *big.Int } // DataHubSubItem is an auto generated low-level Go binding around an user-defined struct. @@ -73,7 +75,7 @@ type DataHubSubRequest struct { // DatahubMetaData contains all meta data concerning the Datahub contract. var DatahubMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ROLE_REPORTER\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"fdpBuyerNameHash\",\"type\":\"bytes32\"}],\"name\":\"bidSub\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"}],\"name\":\"enableSub\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feesCollected\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fundsBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fundsTransfer\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getActiveBidAt\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.ActiveBid\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getActiveBids\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.ActiveBid[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"}],\"name\":\"getActiveBidsByHash\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.ActiveBid\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getAllSubItems\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"unlockKeyLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"validTill\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.SubItem[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"nameHash\",\"type\":\"bytes32\"}],\"name\":\"getAllSubItemsForNameHash\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"unlockKeyLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"validTill\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.SubItem[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"category\",\"type\":\"bytes32\"}],\"name\":\"getCategory\",\"outputs\":[{\"components\":[{\"internalType\":\"uint64[]\",\"name\":\"subIdxs\",\"type\":\"uint64[]\"}],\"internalType\":\"structDataHub.Category\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"getFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getListedSubs\",\"outputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"nameHash\",\"type\":\"bytes32\"}],\"name\":\"getNameHashSubItems\",\"outputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getPortableAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"}],\"name\":\"getSubBy\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"fdpSellerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"swarmLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"earned\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"bids\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"sells\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"reports\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"daysValid\",\"type\":\"uint16\"}],\"internalType\":\"structDataHub.Sub\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getSubByIndex\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"fdpSellerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"swarmLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"earned\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"bids\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"sells\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"reports\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"daysValid\",\"type\":\"uint16\"}],\"internalType\":\"structDataHub.Sub\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"forAddress\",\"type\":\"address\"}],\"name\":\"getSubInfoBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getSubItemAt\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"unlockKeyLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"validTill\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.SubItem\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"start\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"getSubItems\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"unlockKeyLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"validTill\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.SubItem[]\",\"name\":\"items\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256\",\"name\":\"last\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getSubRequestAt\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"fdpBuyerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"buyer\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.SubRequest\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"}],\"name\":\"getSubRequestByHash\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"fdpBuyerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"buyer\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.SubRequest\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getSubRequests\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"fdpBuyerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"buyer\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.SubRequest[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"}],\"name\":\"getSubSubscribers\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getSubs\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"fdpSellerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"swarmLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"earned\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"bids\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"sells\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"reports\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"daysValid\",\"type\":\"uint16\"}],\"internalType\":\"structDataHub.Sub[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getUserStats\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"numSubRequests\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"numSubItems\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"numActiveBids\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"numListedSubs\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"inEscrow\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"fdpSellerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"dataSwarmLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"category\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"podAddress\",\"type\":\"address\"},{\"internalType\":\"uint16\",\"name\":\"daysValid\",\"type\":\"uint16\"}],\"name\":\"listSub\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"marketFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minListingFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"release\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"}],\"name\":\"removeUserActiveBid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"}],\"name\":\"reportSub\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"}],\"name\":\"requestAgain\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"encryptedKeyLocation\",\"type\":\"bytes32\"}],\"name\":\"sellSub\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newFee\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newListingFee\",\"type\":\"uint256\"}],\"name\":\"setListingFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setPortableAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"subscriptionIds\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"subscriptions\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"fdpSellerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"swarmLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"earned\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"bids\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"sells\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"reports\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"daysValid\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ROLE_REPORTER\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"fdpBuyerNameHash\",\"type\":\"bytes32\"}],\"name\":\"bidSub\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"}],\"name\":\"enableSub\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feesCollected\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fundsBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fundsTransfer\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getActiveBidAt\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.ActiveBid\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getActiveBids\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.ActiveBid[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"}],\"name\":\"getActiveBidsByHash\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.ActiveBid\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getAllSubItems\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"unlockKeyLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"validTill\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.SubItem[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"nameHash\",\"type\":\"bytes32\"}],\"name\":\"getAllSubItemsForNameHash\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"unlockKeyLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"validTill\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.SubItem[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"category\",\"type\":\"bytes32\"}],\"name\":\"getCategory\",\"outputs\":[{\"components\":[{\"internalType\":\"uint64[]\",\"name\":\"subIdxs\",\"type\":\"uint64[]\"}],\"internalType\":\"structDataHub.Category\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"getFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getListedSubs\",\"outputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"nameHash\",\"type\":\"bytes32\"}],\"name\":\"getNameHashSubItems\",\"outputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getPortableAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"}],\"name\":\"getSubBy\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"fdpSellerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"swarmLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"earned\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"category\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"bids\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"sells\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"reports\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"daysValid\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.Sub\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getSubByIndex\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"fdpSellerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"swarmLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"earned\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"category\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"bids\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"sells\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"reports\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"daysValid\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.Sub\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"forAddress\",\"type\":\"address\"}],\"name\":\"getSubInfoBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getSubItemAt\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"unlockKeyLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"validTill\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.SubItem\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"start\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"getSubItems\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"unlockKeyLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"validTill\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.SubItem[]\",\"name\":\"items\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256\",\"name\":\"last\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getSubRequestAt\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"fdpBuyerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"buyer\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.SubRequest\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"}],\"name\":\"getSubRequestByHash\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"fdpBuyerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"buyer\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.SubRequest\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getSubRequests\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"fdpBuyerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"buyer\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"served\",\"type\":\"bool\"}],\"internalType\":\"structDataHub.SubRequest[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"}],\"name\":\"getSubSubscribers\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getSubs\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"fdpSellerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"swarmLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"earned\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"category\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"bids\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"sells\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"reports\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"daysValid\",\"type\":\"uint256\"}],\"internalType\":\"structDataHub.Sub[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getUserStats\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"numSubRequests\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"numSubItems\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"numActiveBids\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"numListedSubs\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"inEscrow\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"fdpSellerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"dataSwarmLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"category\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"podAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"daysValid\",\"type\":\"uint256\"}],\"name\":\"listSub\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"marketFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minListingFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"release\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"}],\"name\":\"removeUserActiveBid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"}],\"name\":\"reportSub\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"}],\"name\":\"requestAgain\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"encryptedKeyLocation\",\"type\":\"bytes32\"}],\"name\":\"sellSub\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newFee\",\"type\":\"uint256\"}],\"name\":\"setFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newListingFee\",\"type\":\"uint256\"}],\"name\":\"setListingFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setPortableAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"subscriptionIds\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"subscriptions\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"subHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"fdpSellerNameHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"seller\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"swarmLocation\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"active\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"earned\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"category\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"bids\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"sells\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"reports\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"daysValid\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", } // DatahubABI is the input ABI used to generate the binding from. @@ -177,11 +179,11 @@ func NewDatahubFilterer(address common.Address, filterer bind.ContractFilterer) // bindDatahub binds a generic wrapper to an already deployed contract. func bindDatahub(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(DatahubABI)) + parsed, err := DatahubMetaData.GetAbi() if err != nil { return nil, err } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil } // Call invokes the (constant) contract method with params as input values and @@ -689,7 +691,7 @@ func (_Datahub *DatahubCallerSession) GetRoleAdmin(role [32]byte) ([32]byte, err // GetSubBy is a free data retrieval call binding the contract method 0x1f9ef490. // -// Solidity: function getSubBy(bytes32 subHash) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,uint32,uint32,uint32,uint16)) +// Solidity: function getSubBy(bytes32 subHash) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,bytes32,uint256,uint256,uint256,uint256)) func (_Datahub *DatahubCaller) GetSubBy(opts *bind.CallOpts, subHash [32]byte) (DataHubSub, error) { var out []interface{} err := _Datahub.contract.Call(opts, &out, "getSubBy", subHash) @@ -706,21 +708,21 @@ func (_Datahub *DatahubCaller) GetSubBy(opts *bind.CallOpts, subHash [32]byte) ( // GetSubBy is a free data retrieval call binding the contract method 0x1f9ef490. // -// Solidity: function getSubBy(bytes32 subHash) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,uint32,uint32,uint32,uint16)) +// Solidity: function getSubBy(bytes32 subHash) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,bytes32,uint256,uint256,uint256,uint256)) func (_Datahub *DatahubSession) GetSubBy(subHash [32]byte) (DataHubSub, error) { return _Datahub.Contract.GetSubBy(&_Datahub.CallOpts, subHash) } // GetSubBy is a free data retrieval call binding the contract method 0x1f9ef490. // -// Solidity: function getSubBy(bytes32 subHash) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,uint32,uint32,uint32,uint16)) +// Solidity: function getSubBy(bytes32 subHash) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,bytes32,uint256,uint256,uint256,uint256)) func (_Datahub *DatahubCallerSession) GetSubBy(subHash [32]byte) (DataHubSub, error) { return _Datahub.Contract.GetSubBy(&_Datahub.CallOpts, subHash) } // GetSubByIndex is a free data retrieval call binding the contract method 0xeed5b6e5. // -// Solidity: function getSubByIndex(uint256 index) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,uint32,uint32,uint32,uint16)) +// Solidity: function getSubByIndex(uint256 index) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,bytes32,uint256,uint256,uint256,uint256)) func (_Datahub *DatahubCaller) GetSubByIndex(opts *bind.CallOpts, index *big.Int) (DataHubSub, error) { var out []interface{} err := _Datahub.contract.Call(opts, &out, "getSubByIndex", index) @@ -737,14 +739,14 @@ func (_Datahub *DatahubCaller) GetSubByIndex(opts *bind.CallOpts, index *big.Int // GetSubByIndex is a free data retrieval call binding the contract method 0xeed5b6e5. // -// Solidity: function getSubByIndex(uint256 index) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,uint32,uint32,uint32,uint16)) +// Solidity: function getSubByIndex(uint256 index) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,bytes32,uint256,uint256,uint256,uint256)) func (_Datahub *DatahubSession) GetSubByIndex(index *big.Int) (DataHubSub, error) { return _Datahub.Contract.GetSubByIndex(&_Datahub.CallOpts, index) } // GetSubByIndex is a free data retrieval call binding the contract method 0xeed5b6e5. // -// Solidity: function getSubByIndex(uint256 index) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,uint32,uint32,uint32,uint16)) +// Solidity: function getSubByIndex(uint256 index) view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,bytes32,uint256,uint256,uint256,uint256)) func (_Datahub *DatahubCallerSession) GetSubByIndex(index *big.Int) (DataHubSub, error) { return _Datahub.Contract.GetSubByIndex(&_Datahub.CallOpts, index) } @@ -982,7 +984,7 @@ func (_Datahub *DatahubCallerSession) GetSubSubscribers(subHash [32]byte) ([]com // GetSubs is a free data retrieval call binding the contract method 0xb8fb1bac. // -// Solidity: function getSubs() view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,uint32,uint32,uint32,uint16)[]) +// Solidity: function getSubs() view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,bytes32,uint256,uint256,uint256,uint256)[]) func (_Datahub *DatahubCaller) GetSubs(opts *bind.CallOpts) ([]DataHubSub, error) { var out []interface{} err := _Datahub.contract.Call(opts, &out, "getSubs") @@ -999,14 +1001,14 @@ func (_Datahub *DatahubCaller) GetSubs(opts *bind.CallOpts) ([]DataHubSub, error // GetSubs is a free data retrieval call binding the contract method 0xb8fb1bac. // -// Solidity: function getSubs() view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,uint32,uint32,uint32,uint16)[]) +// Solidity: function getSubs() view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,bytes32,uint256,uint256,uint256,uint256)[]) func (_Datahub *DatahubSession) GetSubs() ([]DataHubSub, error) { return _Datahub.Contract.GetSubs(&_Datahub.CallOpts) } // GetSubs is a free data retrieval call binding the contract method 0xb8fb1bac. // -// Solidity: function getSubs() view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,uint32,uint32,uint32,uint16)[]) +// Solidity: function getSubs() view returns((bytes32,bytes32,address,bytes32,uint256,bool,uint256,bytes32,uint256,uint256,uint256,uint256)[]) func (_Datahub *DatahubCallerSession) GetSubs() ([]DataHubSub, error) { return _Datahub.Contract.GetSubs(&_Datahub.CallOpts) } @@ -1254,7 +1256,7 @@ func (_Datahub *DatahubCallerSession) SubscriptionIds(arg0 [32]byte) (*big.Int, // Subscriptions is a free data retrieval call binding the contract method 0x2d5bbf60. // -// Solidity: function subscriptions(uint256 ) view returns(bytes32 subHash, bytes32 fdpSellerNameHash, address seller, bytes32 swarmLocation, uint256 price, bool active, uint256 earned, uint32 bids, uint32 sells, uint32 reports, uint16 daysValid) +// Solidity: function subscriptions(uint256 ) view returns(bytes32 subHash, bytes32 fdpSellerNameHash, address seller, bytes32 swarmLocation, uint256 price, bool active, uint256 earned, bytes32 category, uint256 bids, uint256 sells, uint256 reports, uint256 daysValid) func (_Datahub *DatahubCaller) Subscriptions(opts *bind.CallOpts, arg0 *big.Int) (struct { SubHash [32]byte FdpSellerNameHash [32]byte @@ -1263,10 +1265,11 @@ func (_Datahub *DatahubCaller) Subscriptions(opts *bind.CallOpts, arg0 *big.Int) Price *big.Int Active bool Earned *big.Int - Bids uint32 - Sells uint32 - Reports uint32 - DaysValid uint16 + Category [32]byte + Bids *big.Int + Sells *big.Int + Reports *big.Int + DaysValid *big.Int }, error) { var out []interface{} err := _Datahub.contract.Call(opts, &out, "subscriptions", arg0) @@ -1279,10 +1282,11 @@ func (_Datahub *DatahubCaller) Subscriptions(opts *bind.CallOpts, arg0 *big.Int) Price *big.Int Active bool Earned *big.Int - Bids uint32 - Sells uint32 - Reports uint32 - DaysValid uint16 + Category [32]byte + Bids *big.Int + Sells *big.Int + Reports *big.Int + DaysValid *big.Int }) if err != nil { return *outstruct, err @@ -1295,10 +1299,11 @@ func (_Datahub *DatahubCaller) Subscriptions(opts *bind.CallOpts, arg0 *big.Int) outstruct.Price = *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) outstruct.Active = *abi.ConvertType(out[5], new(bool)).(*bool) outstruct.Earned = *abi.ConvertType(out[6], new(*big.Int)).(**big.Int) - outstruct.Bids = *abi.ConvertType(out[7], new(uint32)).(*uint32) - outstruct.Sells = *abi.ConvertType(out[8], new(uint32)).(*uint32) - outstruct.Reports = *abi.ConvertType(out[9], new(uint32)).(*uint32) - outstruct.DaysValid = *abi.ConvertType(out[10], new(uint16)).(*uint16) + outstruct.Category = *abi.ConvertType(out[7], new([32]byte)).(*[32]byte) + outstruct.Bids = *abi.ConvertType(out[8], new(*big.Int)).(**big.Int) + outstruct.Sells = *abi.ConvertType(out[9], new(*big.Int)).(**big.Int) + outstruct.Reports = *abi.ConvertType(out[10], new(*big.Int)).(**big.Int) + outstruct.DaysValid = *abi.ConvertType(out[11], new(*big.Int)).(**big.Int) return *outstruct, err @@ -1306,7 +1311,7 @@ func (_Datahub *DatahubCaller) Subscriptions(opts *bind.CallOpts, arg0 *big.Int) // Subscriptions is a free data retrieval call binding the contract method 0x2d5bbf60. // -// Solidity: function subscriptions(uint256 ) view returns(bytes32 subHash, bytes32 fdpSellerNameHash, address seller, bytes32 swarmLocation, uint256 price, bool active, uint256 earned, uint32 bids, uint32 sells, uint32 reports, uint16 daysValid) +// Solidity: function subscriptions(uint256 ) view returns(bytes32 subHash, bytes32 fdpSellerNameHash, address seller, bytes32 swarmLocation, uint256 price, bool active, uint256 earned, bytes32 category, uint256 bids, uint256 sells, uint256 reports, uint256 daysValid) func (_Datahub *DatahubSession) Subscriptions(arg0 *big.Int) (struct { SubHash [32]byte FdpSellerNameHash [32]byte @@ -1315,17 +1320,18 @@ func (_Datahub *DatahubSession) Subscriptions(arg0 *big.Int) (struct { Price *big.Int Active bool Earned *big.Int - Bids uint32 - Sells uint32 - Reports uint32 - DaysValid uint16 + Category [32]byte + Bids *big.Int + Sells *big.Int + Reports *big.Int + DaysValid *big.Int }, error) { return _Datahub.Contract.Subscriptions(&_Datahub.CallOpts, arg0) } // Subscriptions is a free data retrieval call binding the contract method 0x2d5bbf60. // -// Solidity: function subscriptions(uint256 ) view returns(bytes32 subHash, bytes32 fdpSellerNameHash, address seller, bytes32 swarmLocation, uint256 price, bool active, uint256 earned, uint32 bids, uint32 sells, uint32 reports, uint16 daysValid) +// Solidity: function subscriptions(uint256 ) view returns(bytes32 subHash, bytes32 fdpSellerNameHash, address seller, bytes32 swarmLocation, uint256 price, bool active, uint256 earned, bytes32 category, uint256 bids, uint256 sells, uint256 reports, uint256 daysValid) func (_Datahub *DatahubCallerSession) Subscriptions(arg0 *big.Int) (struct { SubHash [32]byte FdpSellerNameHash [32]byte @@ -1334,10 +1340,11 @@ func (_Datahub *DatahubCallerSession) Subscriptions(arg0 *big.Int) (struct { Price *big.Int Active bool Earned *big.Int - Bids uint32 - Sells uint32 - Reports uint32 - DaysValid uint16 + Category [32]byte + Bids *big.Int + Sells *big.Int + Reports *big.Int + DaysValid *big.Int }, error) { return _Datahub.Contract.Subscriptions(&_Datahub.CallOpts, arg0) } @@ -1457,24 +1464,24 @@ func (_Datahub *DatahubTransactorSession) GrantRole(role [32]byte, account commo return _Datahub.Contract.GrantRole(&_Datahub.TransactOpts, role, account) } -// ListSub is a paid mutator transaction binding the contract method 0x202cff8a. +// ListSub is a paid mutator transaction binding the contract method 0x1f59388d. // -// Solidity: function listSub(bytes32 fdpSellerNameHash, bytes32 dataSwarmLocation, uint256 price, bytes32 category, address podAddress, uint16 daysValid) payable returns() -func (_Datahub *DatahubTransactor) ListSub(opts *bind.TransactOpts, fdpSellerNameHash [32]byte, dataSwarmLocation [32]byte, price *big.Int, category [32]byte, podAddress common.Address, daysValid uint16) (*types.Transaction, error) { +// Solidity: function listSub(bytes32 fdpSellerNameHash, bytes32 dataSwarmLocation, uint256 price, bytes32 category, address podAddress, uint256 daysValid) payable returns() +func (_Datahub *DatahubTransactor) ListSub(opts *bind.TransactOpts, fdpSellerNameHash [32]byte, dataSwarmLocation [32]byte, price *big.Int, category [32]byte, podAddress common.Address, daysValid *big.Int) (*types.Transaction, error) { return _Datahub.contract.Transact(opts, "listSub", fdpSellerNameHash, dataSwarmLocation, price, category, podAddress, daysValid) } -// ListSub is a paid mutator transaction binding the contract method 0x202cff8a. +// ListSub is a paid mutator transaction binding the contract method 0x1f59388d. // -// Solidity: function listSub(bytes32 fdpSellerNameHash, bytes32 dataSwarmLocation, uint256 price, bytes32 category, address podAddress, uint16 daysValid) payable returns() -func (_Datahub *DatahubSession) ListSub(fdpSellerNameHash [32]byte, dataSwarmLocation [32]byte, price *big.Int, category [32]byte, podAddress common.Address, daysValid uint16) (*types.Transaction, error) { +// Solidity: function listSub(bytes32 fdpSellerNameHash, bytes32 dataSwarmLocation, uint256 price, bytes32 category, address podAddress, uint256 daysValid) payable returns() +func (_Datahub *DatahubSession) ListSub(fdpSellerNameHash [32]byte, dataSwarmLocation [32]byte, price *big.Int, category [32]byte, podAddress common.Address, daysValid *big.Int) (*types.Transaction, error) { return _Datahub.Contract.ListSub(&_Datahub.TransactOpts, fdpSellerNameHash, dataSwarmLocation, price, category, podAddress, daysValid) } -// ListSub is a paid mutator transaction binding the contract method 0x202cff8a. +// ListSub is a paid mutator transaction binding the contract method 0x1f59388d. // -// Solidity: function listSub(bytes32 fdpSellerNameHash, bytes32 dataSwarmLocation, uint256 price, bytes32 category, address podAddress, uint16 daysValid) payable returns() -func (_Datahub *DatahubTransactorSession) ListSub(fdpSellerNameHash [32]byte, dataSwarmLocation [32]byte, price *big.Int, category [32]byte, podAddress common.Address, daysValid uint16) (*types.Transaction, error) { +// Solidity: function listSub(bytes32 fdpSellerNameHash, bytes32 dataSwarmLocation, uint256 price, bytes32 category, address podAddress, uint256 daysValid) payable returns() +func (_Datahub *DatahubTransactorSession) ListSub(fdpSellerNameHash [32]byte, dataSwarmLocation [32]byte, price *big.Int, category [32]byte, podAddress common.Address, daysValid *big.Int) (*types.Transaction, error) { return _Datahub.Contract.ListSub(&_Datahub.TransactOpts, fdpSellerNameHash, dataSwarmLocation, price, category, podAddress, daysValid) } diff --git a/pkg/dfs/api.go b/pkg/dfs/api.go index 44362065..4be87798 100644 --- a/pkg/dfs/api.go +++ b/pkg/dfs/api.go @@ -39,19 +39,32 @@ const ( // API is the go api for fairOS type API struct { - context context.Context - cancel context.CancelFunc - client blockstore.Client - users *user.Users - logger logging.Logger - tm *taskmanager.TaskManager - sm subscriptionManager.SubscriptionManager + context context.Context + cancel context.CancelFunc + client blockstore.Client + users *user.Users + logger logging.Logger + tm *taskmanager.TaskManager + sm subscriptionManager.SubscriptionManager + feedCacheSize int + feedCacheTTL time.Duration io.Closer } +type Options struct { + BeeApiEndpoint string + Stamp string + EnsConfig *contracts.ENSConfig + SubscriptionConfig *contracts.SubscriptionConfig + Logger logging.Logger + FeedCacheSize int + FeedCacheTTL time.Duration +} + // NewDfsAPI is the main entry point for the df controller. -func NewDfsAPI(ctx context.Context, apiUrl, postageBlockId string, ensConfig *contracts.ENSConfig, subConfig *contracts.SubscriptionConfig, logger logging.Logger) (*API, error) { - ens, err := ethClient.New(ensConfig, logger) +func NewDfsAPI(ctx context.Context, opts *Options) (*API, error) { + logger := opts.Logger + ens, err := ethClient.New(opts.EnsConfig, logger) if err != nil { logger.Errorf("dfs: ens initialisation failed %s", err.Error()) if errors.Is(err, ethClient.ErrWrongChainID) { @@ -59,34 +72,42 @@ func NewDfsAPI(ctx context.Context, apiUrl, postageBlockId string, ensConfig *co } return nil, errEthClient } - c := bee.NewBeeClient(apiUrl, postageBlockId, false, logger) + c := bee.NewBeeClient(opts.BeeApiEndpoint, opts.Stamp, true, logger) if !c.CheckConnection() { logger.Errorf("dfs: bee client initialisation failed") return nil, errBeeClient } - users := user.NewUsers(c, ens, logger) + users := user.NewUsers(c, ens, opts.FeedCacheSize, opts.FeedCacheTTL, logger) var sm subscriptionManager.SubscriptionManager - if subConfig != nil { + if opts.SubscriptionConfig != nil { logger.Infof("dfs: subscriptionManager initialisation") - sm, err = rpc.New(subConfig, logger, c, c) + sm, err = rpc.New(opts.SubscriptionConfig, logger, c, c) if err != nil { logger.Errorf("dfs: subscriptionManager initialisation failed %s", err.Error()) return nil, errSubManager } } + // Setting cache size 0 will disable the cache. This is to change the default behaviour of lru itself. + // We have this -1 check hard coded in the feed package. -1 will disable the feed pool off. and write directly to swarm. + if opts.FeedCacheSize == 0 { + opts.FeedCacheSize = -1 + } + // discard tm logs as it creates too much noise tmLogger := logging.New(io.Discard, 0) ctx2, cancel := context.WithCancel(ctx) return &API{ - context: ctx2, - cancel: cancel, - client: c, - users: users, - logger: logger, - tm: taskmanager.New(10, defaultMaxWorkers, time.Second*15, tmLogger), - sm: sm, + context: ctx2, + cancel: cancel, + client: c, + users: users, + logger: logger, + tm: taskmanager.New(10, defaultMaxWorkers, time.Second*15, tmLogger), + sm: sm, + feedCacheSize: opts.FeedCacheSize, + feedCacheTTL: opts.FeedCacheTTL, }, nil } @@ -105,6 +126,14 @@ func NewMockDfsAPI(client blockstore.Client, users *user.Users, logger logging.L // Close stops the taskmanager func (a *API) Close() error { + users := a.users.GetUsersLoggedIn() + for sessionId, v := range users { + err := a.LogoutUser(sessionId) + if err != nil { + a.logger.Errorf("dfs: error logging out user %s", v.GetUserName()) + } + } + ctx, cancel := context.WithTimeout(a.context, time.Minute) defer func() { cancel() diff --git a/pkg/dfs/doc_api.go b/pkg/dfs/doc_api.go index c2de067d..16c295d5 100644 --- a/pkg/dfs/doc_api.go +++ b/pkg/dfs/doc_api.go @@ -50,6 +50,22 @@ func (a *API) DocOpen(sessionId, podName, name string) error { return podInfo.GetDocStore().OpenDocumentDB(name, podInfo.GetPodPassword()) } +// IsDBOpened is a controller function which checks if documentDB is open +func (a *API) IsDBOpened(sessionId, podName, name string) (bool, error) { + // get the logged-in user information + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return false, ErrUserNotLoggedIn + } + + podInfo, _, err := ui.GetPod().GetPodInfo(podName) + if err != nil { + return false, err + } + + return podInfo.GetDocStore().IsDBOpened(name), nil +} + // DocDelete is a controller function which does all the checks before deleting a documentDB. func (a *API) DocDelete(sessionId, podName, name string) error { // get the logged-in user information diff --git a/pkg/dfs/fs_api.go b/pkg/dfs/fs_api.go index 1f8d019b..b7d32561 100644 --- a/pkg/dfs/fs_api.go +++ b/pkg/dfs/fs_api.go @@ -17,6 +17,7 @@ limitations under the License. package dfs import ( + "errors" "fmt" "io" "path/filepath" @@ -31,15 +32,19 @@ import ( // Mkdir is a controller function which validates if the user is logged-in, // pod is open and calls the make directory function in the dir object. -func (a *API) Mkdir(podName, dirToCreateWithPath, sessionId string, mode uint32) error { +func (a *API) Mkdir(podName, dirToCreateWithPath, sessionId string, mode uint32, isGroup bool) error { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return ErrUserNotLoggedIn } - // get the dir object and make directory - podInfo, podPassword, err := ui.GetPod().GetPodInfo(podName) + podInfo, podPassword, err := &pod.Info{}, "", error(nil) + if isGroup { + podInfo, podPassword, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, podPassword, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return err } @@ -49,15 +54,19 @@ func (a *API) Mkdir(podName, dirToCreateWithPath, sessionId string, mode uint32) // RenameDir is a controller function which validates if the user is logged-in, // pod is open and calls the rename directory function in the dir object. -func (a *API) RenameDir(podName, dirToRenameWithPath, newName, sessionId string) error { +func (a *API) RenameDir(podName, dirToRenameWithPath, newName, sessionId string, isGroup bool) error { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return ErrUserNotLoggedIn } - // get the dir object and rename directory - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return err } @@ -67,35 +76,43 @@ func (a *API) RenameDir(podName, dirToRenameWithPath, newName, sessionId string) // IsDirPresent is a controller function which validates if the user is logged-in, // pod is open and calls the dir object to check if the directory is present. -func (a *API) IsDirPresent(podName, directoryNameWithPath, sessionId string) (bool, error) { +func (a *API) IsDirPresent(podName, directoryNameWithPath, sessionId string, isGroup bool) (bool, error) { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return false, ErrUserNotLoggedIn } - // get pod Info - podInfo, podPassword, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return false, err } directory := podInfo.GetDirectory() directoryNameWithPath = filepath.ToSlash(directoryNameWithPath) - dirPresent := directory.IsDirectoryPresent(directoryNameWithPath, podPassword) + dirPresent := directory.IsDirectoryPresent(directoryNameWithPath, podInfo.GetPodPassword()) return dirPresent, nil } // RmDir is a controller function which validates if the user is logged-in, // pod is open and calls the dir object to remove the supplied directory. -func (a *API) RmDir(podName, directoryNameWithPath, sessionId string) error { +func (a *API) RmDir(podName, directoryNameWithPath, sessionId string, isGroup bool) error { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return ErrUserNotLoggedIn } - // get the dir object and remove directory - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return err } @@ -105,15 +122,19 @@ func (a *API) RmDir(podName, directoryNameWithPath, sessionId string) error { // ListDir is a controller function which validates if the user is logged-in, // pod is open and calls the dir object to list the contents of the supplied directory. -func (a *API) ListDir(podName, currentDir, sessionId string) ([]dir.Entry, []f.Entry, error) { +func (a *API) ListDir(podName, currentDir, sessionId string, isGroup bool) ([]dir.Entry, []f.Entry, error) { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return nil, nil, ErrUserNotLoggedIn } - // get the dir object and list directory - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return nil, nil, err } @@ -121,7 +142,9 @@ func (a *API) ListDir(podName, currentDir, sessionId string) ([]dir.Entry, []f.E // check if directory present totalPath := utils.CombinePathAndFile(currentDir, "") - if directory.GetInode(podInfo.GetPodPassword(), totalPath) == nil { + _, err = directory.GetInode(podInfo.GetPodPassword(), totalPath) + if err != nil { + a.logger.Errorf("dir not found: %s: %s", currentDir, err.Error()) return nil, nil, dir.ErrDirectoryNotPresent } dEntries, fileList, err := directory.ListDir(currentDir, podInfo.GetPodPassword()) @@ -138,15 +161,19 @@ func (a *API) ListDir(podName, currentDir, sessionId string) ([]dir.Entry, []f.E // DirectoryStat is a controller function which validates if the user is logged-in, // pod is open and calls the dir object to get the information about the given directory. -func (a *API) DirectoryStat(podName, directoryPath, sessionId string) (*dir.Stats, error) { +func (a *API) DirectoryStat(podName, directoryPath, sessionId string, isGroup bool) (*dir.Stats, error) { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return nil, ErrUserNotLoggedIn } - // get the dir object and stat directory - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return nil, err } @@ -154,8 +181,8 @@ func (a *API) DirectoryStat(podName, directoryPath, sessionId string) (*dir.Stat directoryPath = filepath.ToSlash(directoryPath) if directoryPath != utils.PathSeparator { parent := filepath.ToSlash(filepath.Dir(directoryPath)) - inode := directory.GetInode(podInfo.GetPodPassword(), parent) - if inode == nil { + inode, err := directory.GetInode(podInfo.GetPodPassword(), parent) + if err != nil { return nil, dir.ErrDirectoryNotPresent } found := false @@ -176,21 +203,25 @@ func (a *API) DirectoryStat(podName, directoryPath, sessionId string) (*dir.Stat // DirectoryInode is a controller function which validates if the user is logged-in, // pod is open and calls the dir object to get the inode info about the given directory. -func (a *API) DirectoryInode(podName, directoryName, sessionId string) (*dir.Inode, error) { +func (a *API) DirectoryInode(podName, directoryName, sessionId string, isGroup bool) (*dir.Inode, error) { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return nil, ErrUserNotLoggedIn } - // get the dir object and stat directory - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return nil, err } directory := podInfo.GetDirectory() - inode := directory.GetInode(podInfo.GetPodPassword(), directoryName) - if inode == nil { + inode, err := directory.GetInode(podInfo.GetPodPassword(), directoryName) + if err != nil { a.logger.Errorf("dir not found: %s", directoryName) return nil, fmt.Errorf("dir not found") } @@ -199,15 +230,19 @@ func (a *API) DirectoryInode(podName, directoryName, sessionId string) (*dir.Ino // ChmodDir is a controller function which validates if the user is logged-in, // pod is open and calls changes mode of a directory -func (a *API) ChmodDir(podName, directoryNameWithPath, sessionId string, mode uint32) error { +func (a *API) ChmodDir(podName, directoryNameWithPath, sessionId string, mode uint32, isGroup bool) error { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return ErrUserNotLoggedIn } - // get podInfo and construct the path - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return err } @@ -220,14 +255,19 @@ func (a *API) ChmodDir(podName, directoryNameWithPath, sessionId string, mode ui // DeleteFile is a controller function which validates if the user is logged-in, // pod is open and delete the file. It also removes the file entry from the parent // directory. -func (a *API) DeleteFile(podName, podFileWithPath, sessionId string) error { +func (a *API) DeleteFile(podName, podFileWithPath, sessionId string, isGroup bool) error { // get the loggedin user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return ErrUserNotLoggedIn } - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return err } @@ -241,7 +281,7 @@ func (a *API) DeleteFile(podName, podFileWithPath, sessionId string) error { file := podInfo.GetFile() err = file.RmFile(podFileWithPath, podInfo.GetPodPassword()) if err != nil { - if err == f.ErrDeletedFeed { + if errors.Is(err, f.ErrDeletedFeed) { return pod.ErrInvalidFile } return err @@ -255,21 +295,26 @@ func (a *API) DeleteFile(podName, podFileWithPath, sessionId string) error { // FileStat is a controller function which validates if the user is logged-in, // pod is open and gets the information about the file. -func (a *API) FileStat(podName, podFileWithPath, sessionId string) (*f.Stats, error) { +func (a *API) FileStat(podName, podFileWithPath, sessionId string, isGroup bool) (*f.Stats, error) { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return nil, ErrUserNotLoggedIn } - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return nil, err } podFileWithPath = filepath.ToSlash(podFileWithPath) directory := podInfo.GetDirectory() - inode := directory.GetInode(podInfo.GetPodPassword(), filepath.ToSlash(filepath.Dir(podFileWithPath))) - if inode != nil { + inode, err := directory.GetInode(podInfo.GetPodPassword(), filepath.ToSlash(filepath.Dir(podFileWithPath))) + if err == nil { found := false fileName := filepath.Base(podFileWithPath) for _, name := range inode.FileOrDirNames { @@ -290,17 +335,26 @@ func (a *API) FileStat(podName, podFileWithPath, sessionId string) (*f.Stats, er // UploadFile is a controller function which validates if the user is logged-in, // // pod is open and calls the upload function. -func (a *API) UploadFile(podName, podFileName, sessionId string, fileSize int64, fd io.Reader, podPath, compression string, blockSize, mode uint32, overwrite bool) error { +func (a *API) UploadFile(podName, podFileName, sessionId string, fileSize int64, fd io.Reader, podPath, compression string, blockSize, mode uint32, overwrite, isGroup bool) error { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return ErrUserNotLoggedIn } - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return err } + + if podInfo == nil { + return errors.New("pod/group does not exist") + } file := podInfo.GetFile() directory := podInfo.GetDirectory() podPath = filepath.ToSlash(podPath) @@ -335,14 +389,19 @@ func (a *API) UploadFile(podName, podFileName, sessionId string, fileSize int64, // RenameFile is a controller function which validates if the user is logged-in, // // pod is open and calls renaming of a file -func (a *API) RenameFile(podName, fileNameWithPath, newFileNameWithPath, sessionId string) error { +func (a *API) RenameFile(podName, fileNameWithPath, newFileNameWithPath, sessionId string, isGroup bool) error { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return ErrUserNotLoggedIn } - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return err } @@ -378,15 +437,19 @@ func (a *API) RenameFile(podName, fileNameWithPath, newFileNameWithPath, session // DownloadFile is a controller function which validates if the user is logged-in, // pod is open and calls the download function. -func (a *API) DownloadFile(podName, podFileWithPath, sessionId string) (io.ReadCloser, uint64, error) { +func (a *API) DownloadFile(podName, podFileWithPath, sessionId string, isGroup bool) (io.ReadCloser, uint64, error) { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return nil, 0, ErrUserNotLoggedIn } - // get podInfo and construct the path - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return nil, 0, err } @@ -403,14 +466,19 @@ func (a *API) DownloadFile(podName, podFileWithPath, sessionId string) (io.ReadC // WriteAtFile is a controller function which writes a file from a given offset // // pod is open and calls writeAt of a file -func (a *API) WriteAtFile(podName, fileNameWithPath, sessionId string, update io.Reader, offset uint64, truncate bool) (int, error) { +func (a *API) WriteAtFile(podName, fileNameWithPath, sessionId string, update io.Reader, offset uint64, truncate, isGroup bool) (int, error) { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return 0, ErrUserNotLoggedIn } - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return 0, err } @@ -426,15 +494,19 @@ func (a *API) WriteAtFile(podName, fileNameWithPath, sessionId string, update io // ReadSeekCloser is a controller function which validates if the user is logged-in, // pod is open and calls the download function. -func (a *API) ReadSeekCloser(podName, podFileWithPath, sessionId string) (io.ReadSeekCloser, uint64, error) { +func (a *API) ReadSeekCloser(podName, podFileWithPath, sessionId string, isGroup bool) (io.ReadSeekCloser, uint64, error) { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return nil, 0, ErrUserNotLoggedIn } - // get podInfo and construct the path - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return nil, 0, err } @@ -450,15 +522,19 @@ func (a *API) ReadSeekCloser(podName, podFileWithPath, sessionId string) (io.Rea // ShareFile is a controller function which validates if the user is logged-in, // pod is open and calls the shareFile function. -func (a *API) ShareFile(podName, podFileWithPath, destinationUser, sessionId string) (string, error) { +func (a *API) ShareFile(podName, podFileWithPath, destinationUser, sessionId string, isGroup bool) (string, error) { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return "", ErrUserNotLoggedIn } - // get podInfo and construct the path - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return "", err } @@ -497,15 +573,19 @@ func (a *API) ReceiveInfo(sessionId, ref string) (*user.ReceiveFileInfo, error) // StatusFile is a controller function which validates if the user is logged-in, // pod is open and calls the sync status of file function. -func (a *API) StatusFile(podName, podFileWithPath, sessionId string) (int64, int64, int64, error) { +func (a *API) StatusFile(podName, podFileWithPath, sessionId string, isGroup bool) (int64, int64, int64, error) { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return 0, 0, 0, ErrUserNotLoggedIn } - // get podInfo and construct the path - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return 0, 0, 0, err } @@ -517,15 +597,19 @@ func (a *API) StatusFile(podName, podFileWithPath, sessionId string) (int64, int // ChmodFile is a controller function which validates if the user is logged-in, // pod is open and calls changes mode of a file -func (a *API) ChmodFile(podName, podFileWithPath, sessionId string, mode uint32) error { +func (a *API) ChmodFile(podName, podFileWithPath, sessionId string, mode uint32, isGroup bool) error { // get the logged-in user information ui := a.users.GetLoggedInUserInfo(sessionId) if ui == nil { return ErrUserNotLoggedIn } - // get podInfo and construct the path - podInfo, _, err := ui.GetPod().GetPodInfo(podName) + podInfo, err := &pod.Info{}, error(nil) + if isGroup { + podInfo, _, err = ui.GetGroup().GetGroupInfoFromMap(podName) + } else { + podInfo, _, err = ui.GetPod().GetPodInfo(podName) + } if err != nil { return err } diff --git a/pkg/dfs/group_api.go b/pkg/dfs/group_api.go new file mode 100644 index 00000000..a000b62a --- /dev/null +++ b/pkg/dfs/group_api.go @@ -0,0 +1,172 @@ +package dfs + +import ( + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" +) + +// CreateGroup creates a new group +func (a *API) CreateGroup(sessionId, groupName string) (*pod.Info, error) { + // get the loggedin user information + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return nil, ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + // create a new group + return group.CreateGroup(groupName) +} + +// RemoveGroup deletes an existing group +func (a *API) RemoveGroup(sessionId, groupName string) error { + // get the loggedin user information + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + return group.RemoveGroup(groupName) +} + +// RemoveSharedGroup deletes an existing group from shared list +func (a *API) RemoveSharedGroup(sessionId, groupName string) error { + // get the loggedin user information + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + return group.RemoveSharedGroup(groupName) +} + +func (a *API) ListGroups(sessionId string) (*pod.GroupList, error) { + // get the loggedin user information + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return nil, ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + return group.ListGroup() +} + +func (a *API) OpenGroup(sessionId, groupName string) (*pod.Info, error) { + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return nil, ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + return group.OpenGroup(groupName) +} + +func (a *API) CloseGroup(sessionId, groupName string) error { + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + return group.CloseGroup(groupName) +} + +func (a *API) AddMember(sessionId, groupName, username string, permission uint8) ([]byte, error) { + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return nil, ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + nh, err := a.users.GetNameHash(username) + if err != nil { + return nil, err + } + + addr, pub, err := a.users.GetUserInfoFromENS(nh) + if err != nil { + return nil, err + } + return group.AddMember(groupName, addr, pub, permission) +} + +func (a *API) AcceptGroupInvite(sessionId string, ref []byte) error { + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + return group.AcceptGroupInvite(ref) +} + +func (a *API) RemoveMember(sessionId, groupName, username string) error { + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + nh, err := a.users.GetNameHash(username) + if err != nil { + return err + } + + addr, _, err := a.users.GetUserInfoFromENS(nh) + if err != nil { + return err + } + return group.RemoveMember(groupName, addr) +} + +func (a *API) UpdatePermission(sessionId, groupName, username string, permission uint8) error { + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + nh, err := a.users.GetNameHash(username) + if err != nil { + return err + } + + addr, _, err := a.users.GetUserInfoFromENS(nh) + if err != nil { + return err + } + return group.UpdatePermission(groupName, addr, permission) +} + +func (a *API) GetPermission(sessionId, groupName string) (uint8, error) { + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return 0, ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + return group.GetPermission(groupName) +} + +func (a *API) GetGroupMembers(sessionId, groupName string) (map[string]uint8, error) { + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return nil, ErrUserNotLoggedIn + } + + group := ui.GetGroup() + + return group.GetGroupMembers(groupName) +} diff --git a/pkg/dfs/kv_api.go b/pkg/dfs/kv_api.go index e1d52760..261452cb 100644 --- a/pkg/dfs/kv_api.go +++ b/pkg/dfs/kv_api.go @@ -20,6 +20,11 @@ import ( "github.com/fairdatasociety/fairOS-dfs/pkg/collection" ) +type KVGetter interface { + KVGet(name, key string) ([]string, []byte, error) + OpenKVTable(name, encryptionPassword string) error +} + // KVCreate does validation checks and calls the create KVtable function. func (a *API) KVCreate(sessionId, podName, name string, indexType collection.IndexType) error { // get the logged-in user information @@ -97,7 +102,7 @@ func (a *API) KVCount(sessionId, podName, name string) (*collection.TableKeyCoun return nil, err } - return podInfo.GetKVStore().KVCount(name, podInfo.GetPodPassword()) + return podInfo.GetKVStore().KVCount(name) } // KVPut does validation checks and calls the put KVtable function. diff --git a/pkg/dfs/pod_api.go b/pkg/dfs/pod_api.go index 2dc46a6d..1fad4715 100644 --- a/pkg/dfs/pod_api.go +++ b/pkg/dfs/pod_api.go @@ -20,6 +20,7 @@ import ( "context" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -27,9 +28,8 @@ import ( "strconv" "strings" - c "github.com/fairdatasociety/fairOS-dfs/pkg/collection" - "github.com/fairdatasociety/fairOS-dfs/pkg/account" + c "github.com/fairdatasociety/fairOS-dfs/pkg/collection" "github.com/fairdatasociety/fairOS-dfs/pkg/contracts/datahub" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" @@ -53,7 +53,6 @@ func (a *API) CreatePod(podName, sessionId string) (*pod.Info, error) { if err != nil { return nil, err } - // Add podName in the login user session ui.AddPodName(podName, pi) return pi, nil @@ -66,7 +65,6 @@ func (a *API) DeletePod(podName, sessionId string) error { if ui == nil { return ErrUserNotLoggedIn } - // delete all the directory, files, and database tables under this pod from // the Swarm network. podInfo, _, err := ui.GetPod().GetPodInfo(podName) @@ -74,7 +72,6 @@ func (a *API) DeletePod(podName, sessionId string) error { return err } directory := podInfo.GetDirectory() - // check if this is a shared pod if podInfo.GetFeed().IsReadOnlyFeed() { // delete the pod and close if it is opened @@ -87,18 +84,15 @@ func (a *API) DeletePod(podName, sessionId string) error { ui.RemovePodName(podName) return nil } - err = directory.RmRootDir(podInfo.GetPodPassword()) - if err != nil { + if err != nil && !errors.Is(err, file.ErrFileNotFound) { return err } - // delete the pod and close if it is opened err = ui.GetPod().DeleteOwnPod(podName) if err != nil { return err } - ui.RemovePodName(podName) return nil } @@ -138,6 +132,16 @@ func (a *API) ClosePod(podName, sessionId string) error { return nil } +// CommitPodFeeds commits feed for a pod on swarm +func (a *API) CommitPodFeeds(podName, sessionId string) error { + // get the loggedin user information + ui := a.users.GetLoggedInUserInfo(sessionId) + if ui == nil { + return ErrUserNotLoggedIn + } + return ui.GetPod().CommitFeeds(podName) +} + // PodStat returns the pod stat func (a *API) PodStat(podName, sessionId string) (*pod.Stat, error) { // get the logged-in user information @@ -268,9 +272,9 @@ func (a *API) PublicPodFileDownload(pod *pod.ShareInfo, filePath string) (io.Rea address := utils.HexToAddress(pod.Address) accountInfo.SetAddress(address) - fd := feed.New(accountInfo, a.client, a.logger) + fd := feed.New(accountInfo, a.client, a.feedCacheSize, a.feedCacheTTL, a.logger) topic := utils.HashString(filePath) - _, metaBytes, err := fd.GetFeedData(topic, accountInfo.GetAddress(), []byte(pod.Password)) + _, metaBytes, err := fd.GetFeedData(topic, accountInfo.GetAddress(), []byte(pod.Password), false) if err != nil { return nil, 0, err } @@ -308,7 +312,7 @@ func (a *API) PublicPodKVEntryGet(pod *pod.ShareInfo, name, key string) ([]strin address := utils.HexToAddress(pod.Address) accountInfo.SetAddress(address) - fd := feed.New(accountInfo, a.client, a.logger) + fd := feed.New(accountInfo, a.client, a.feedCacheSize, a.feedCacheTTL, a.logger) kvStore := c.NewKeyValueStore(pod.PodName, fd, accountInfo, address, a.client, a.logger) err := kvStore.OpenKVTable(name, pod.Password) @@ -319,6 +323,16 @@ func (a *API) PublicPodKVEntryGet(pod *pod.ShareInfo, name, key string) ([]strin return kvStore.KVGet(name, key) } +// PublicPodKVGetter gets a kv store getter interface +func (a *API) PublicPodKVGetter(pod *pod.ShareInfo) KVGetter { + accountInfo := &account.Info{} + address := utils.HexToAddress(pod.Address) + accountInfo.SetAddress(address) + + fd := feed.New(accountInfo, a.client, a.feedCacheSize, a.feedCacheTTL, a.logger) + return c.NewKeyValueStore(pod.PodName, fd, accountInfo, address, a.client, a.logger) +} + // PublicPodDisLs lists a directory from a public pod func (a *API) PublicPodDisLs(pod *pod.ShareInfo, dirPathToLs string) ([]dir.Entry, []file.Entry, error) { @@ -326,11 +340,11 @@ func (a *API) PublicPodDisLs(pod *pod.ShareInfo, dirPathToLs string) ([]dir.Entr address := utils.HexToAddress(pod.Address) accountInfo.SetAddress(address) - fd := feed.New(accountInfo, a.client, a.logger) + fd := feed.New(accountInfo, a.client, a.feedCacheSize, a.feedCacheTTL, a.logger) dirNameWithPath := filepath.ToSlash(dirPathToLs) topic := utils.HashString(dirNameWithPath) - _, data, err := fd.GetFeedData(topic, accountInfo.GetAddress(), []byte(pod.Password)) + _, data, err := fd.GetFeedData(topic, accountInfo.GetAddress(), []byte(pod.Password), false) if err != nil { // skipcq: TCV-001 if dirNameWithPath == utils.PathSeparator { return nil, nil, nil @@ -352,7 +366,7 @@ func (a *API) PublicPodDisLs(pod *pod.ShareInfo, dirPathToLs string) ([]dir.Entr dirPath := utils.CombinePathAndFile(dirNameWithPath, dirName) dirTopic := utils.HashString(dirPath) - _, data, err := fd.GetFeedData(dirTopic, accountInfo.GetAddress(), []byte(pod.Password)) + _, data, err := fd.GetFeedData(dirTopic, accountInfo.GetAddress(), []byte(pod.Password), false) if err != nil { // skipcq: TCV-001 return nil, nil, fmt.Errorf("list dir : %v", err) } @@ -381,7 +395,7 @@ func (a *API) PublicPodDisLs(pod *pod.ShareInfo, dirPathToLs string) ([]dir.Entr for _, filePath := range files { fileTopic := utils.HashString(utils.CombinePathAndFile(filePath, "")) - _, data, err := fd.GetFeedData(fileTopic, accountInfo.GetAddress(), []byte(pod.Password)) + _, data, err := fd.GetFeedData(fileTopic, accountInfo.GetAddress(), []byte(pod.Password), false) if err != nil { // skipcq: TCV-001 return nil, nil, fmt.Errorf("file mtdt : %v", err) } diff --git a/pkg/dfs/user_api.go b/pkg/dfs/user_api.go index 02139d52..37578cc2 100644 --- a/pkg/dfs/user_api.go +++ b/pkg/dfs/user_api.go @@ -43,6 +43,12 @@ func (a *API) LogoutUser(sessionId string) error { return ErrUserNotLoggedIn } + err := ui.GetPod().CloseAllPods() + if err != nil { + a.logger.Errorf("error closing all pods: %v", err) + } + ui.GetFeed().CommitFeeds() + return a.users.LogoutUser(ui.GetUserName(), sessionId) } diff --git a/pkg/dir/chmod.go b/pkg/dir/chmod.go index 47cf892b..42682b56 100644 --- a/pkg/dir/chmod.go +++ b/pkg/dir/chmod.go @@ -1,26 +1,13 @@ package dir import ( - "encoding/json" "fmt" "time" - - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" ) // Chmod does all the validation for the existence of the file and changes file mode func (d *Directory) Chmod(dirNameWithPath, podPassword string, mode uint32) error { - topic := utils.HashString(dirNameWithPath) - _, data, err := d.fd.GetFeedData(topic, d.getAddress(), []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return fmt.Errorf("dir chmod: %v", err) - } - if string(data) == utils.DeletedFeedMagicWord { - return ErrDirectoryNotPresent - } - - var dirInode Inode - err = json.Unmarshal(data, &dirInode) + dirInode, err := d.GetInode(podPassword, dirNameWithPath) if err != nil { // skipcq: TCV-001 return fmt.Errorf("dir chmod: %v", err) } @@ -31,14 +18,5 @@ func (d *Directory) Chmod(dirNameWithPath, podPassword string, mode uint32) erro dirInode.Meta.Mode = S_IFDIR | mode dirInode.Meta.AccessTime = time.Now().Unix() - metaBytes, err := json.Marshal(dirInode) - if err != nil { // skipcq: TCV-001 - return err - } - _, err = d.fd.UpdateFeed(d.userAddress, topic, metaBytes, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - d.AddToDirectoryMap(dirNameWithPath, &dirInode) - return nil + return d.SetInode(podPassword, dirInode) } diff --git a/pkg/dir/chmod_test.go b/pkg/dir/chmod_test.go index ae1e0c9e..5bfc2bae 100644 --- a/pkg/dir/chmod_test.go +++ b/pkg/dir/chmod_test.go @@ -7,20 +7,33 @@ import ( "testing" "time" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" - bm "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" - fm "github.com/fairdatasociety/fairOS-dfs/pkg/file/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestChmod(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -30,13 +43,13 @@ func TestChmod(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) - mockFile := fm.NewMockFile() tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) t.Run("chmod-dir", func(t *testing.T) { podPassword, _ := utils.GetRandString(pod.PasswordLength) diff --git a/pkg/dir/dir.go b/pkg/dir/dir.go index 5936a7f0..2bae9937 100644 --- a/pkg/dir/dir.go +++ b/pkg/dir/dir.go @@ -18,7 +18,6 @@ package dir import ( "context" - "encoding/json" "fmt" "strconv" "sync" @@ -150,15 +149,11 @@ func newLsTask(d *Directory, topic []byte, path, podPassword string, l *[]Entry, // Execute func (lt *lsTask) Execute(context.Context) error { defer lt.wg.Done() - _, data, err := lt.d.fd.GetFeedData(lt.topic, lt.d.getAddress(), []byte(lt.podPassword)) - if err != nil { // skipcq: TCV-001 - return fmt.Errorf("list dir : %v", err) - } - var dirInode *Inode - err = json.Unmarshal(data, &dirInode) + dirInode, err := lt.d.GetInode(lt.podPassword, lt.path) if err != nil { // skipcq: TCV-001 return fmt.Errorf("list dir : %v", err) } + entry := Entry{ Name: dirInode.Meta.Name, ContentType: MimeTypeDirectory, // per RFC2425 diff --git a/pkg/dir/dir_present.go b/pkg/dir/dir_present.go index 39ffc56f..c9e0cb92 100644 --- a/pkg/dir/dir_present.go +++ b/pkg/dir/dir_present.go @@ -16,16 +16,11 @@ limitations under the License. package dir -import ( - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" -) - // IsDirectoryPresent this function check if a given directory is present inside the pod. func (d *Directory) IsDirectoryPresent(directoryNameWithPath, podPassword string) bool { - topic := utils.HashString(directoryNameWithPath) - _, metaBytes, err := d.fd.GetFeedData(topic, d.userAddress, []byte(podPassword)) - if string(metaBytes) == utils.DeletedFeedMagicWord { + in, err := d.GetInode(podPassword, directoryNameWithPath) + if err != nil { return false } - return err == nil + return in != nil } diff --git a/pkg/dir/dir_present_test.go b/pkg/dir/dir_present_test.go index 537bd627..7ed1e552 100644 --- a/pkg/dir/dir_present_test.go +++ b/pkg/dir/dir_present_test.go @@ -22,22 +22,33 @@ import ( "testing" "time" - "github.com/fairdatasociety/fairOS-dfs/pkg/pod" - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" - "github.com/plexsysio/taskmanager" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" - bm "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" - fm "github.com/fairdatasociety/fairOS-dfs/pkg/file/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestDirPresent(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -47,13 +58,13 @@ func TestDirPresent(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) - mockFile := fm.NewMockFile() tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) t.Run("dir-present", func(t *testing.T) { podPassword, _ := utils.GetRandString(pod.PasswordLength) diff --git a/pkg/dir/dir_test.go b/pkg/dir/dir_test.go index 9f84ad6b..32c8576f 100644 --- a/pkg/dir/dir_test.go +++ b/pkg/dir/dir_test.go @@ -6,22 +6,32 @@ import ( "testing" "time" - "github.com/fairdatasociety/fairOS-dfs/pkg/pod" - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" - - "github.com/plexsysio/taskmanager" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" - bm "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" - fm "github.com/fairdatasociety/fairOS-dfs/pkg/file/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestDirRmAllFromMap(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -31,13 +41,13 @@ func TestDirRmAllFromMap(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) - mockFile := fm.NewMockFile() tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) t.Run("dir-rm-all-from-map", func(t *testing.T) { podPassword, _ := utils.GetRandString(pod.PasswordLength) @@ -62,7 +72,7 @@ func TestDirRmAllFromMap(t *testing.T) { } dirObject.RemoveAllFromDirectoryMap() - node := dirObject.GetInode(podPassword, "/baseDir") + node, _ := dirObject.GetInode(podPassword, "/baseDir") if node == nil { t.Fatal("node should not be nil, metadata should be available in blockstore") } @@ -71,7 +81,7 @@ func TestDirRmAllFromMap(t *testing.T) { if err != nil { t.Fatal(err) } - node = dirObject.GetInode(podPassword, "/baseDir") + node, _ = dirObject.GetInode(podPassword, "/baseDir") if node != nil { t.Fatal("node should be nil") } diff --git a/pkg/dir/inode.go b/pkg/dir/inode.go index 43831827..d60d81cd 100644 --- a/pkg/dir/inode.go +++ b/pkg/dir/inode.go @@ -17,9 +17,14 @@ limitations under the License. package dir import ( + "bufio" + "bytes" "encoding/json" "errors" + "io" + "path/filepath" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" ) @@ -61,21 +66,80 @@ func (in *Inode) Unmarshal(data []byte) error { } // GetInode returns the inode of the given directory -func (d *Directory) GetInode(podPassword, dirNameWithPath string) *Inode { +func (d *Directory) GetInode(podPassword, dirNameWithPath string) (*Inode, error) { node := d.GetDirFromDirectoryMap(dirNameWithPath) if node != nil { - return node + return node, nil } - topic := utils.HashString(dirNameWithPath) - _, data, err := d.fd.GetFeedData(topic, d.getAddress(), []byte(podPassword)) + var inode Inode + var data []byte + r, _, err := d.file.Download(utils.CombinePathAndFile(dirNameWithPath, indexFileName), podPassword) if err != nil { // skipcq: TCV-001 - return nil + topic := utils.HashString(dirNameWithPath) + _, data, err = d.fd.GetFeedData(topic, d.getAddress(), []byte(podPassword), false) + if err != nil { // skipcq: TCV-001 + return nil, ErrDirectoryNotPresent + } + err = inode.Unmarshal(data) + if err != nil { // skipcq: TCV-001 + return nil, err + } + err = d.SetInode(podPassword, &inode) + if err != nil { // skipcq: TCV-001 + return nil, err + } + + // ignore delete error + _ = d.fd.DeleteFeedFromTopic(topic, d.getAddress()) + } else { + data, err = io.ReadAll(r) + if err != nil { // skipcq: TCV-001 + return nil, err + } + err = inode.Unmarshal(data) + if err != nil { // skipcq: TCV-001 + return nil, err + } } - var inode Inode - err = inode.Unmarshal(data) + d.AddToDirectoryMap(dirNameWithPath, &inode) + return &inode, nil +} + +// SetInode saves the inode of the given directory +func (d *Directory) SetInode(podPassword string, iNode *Inode) error { + totalPath := utils.CombinePathAndFile(iNode.Meta.Path, iNode.Meta.Name) + data, err := json.Marshal(iNode) if err != nil { // skipcq: TCV-001 + return err + } + + err = d.file.Upload(bufio.NewReader(bytes.NewBuffer(data)), indexFileName, int64(len(data)), file.MinBlockSize, 0, totalPath, "gzip", podPassword) + if err != nil { + return err + } + d.AddToDirectoryMap(totalPath, iNode) + return nil +} + +// RemoveInode removes the inode of the given directory +func (d *Directory) RemoveInode(podPassword, dirNameWithPath string) error { + parentPath := filepath.ToSlash(filepath.Dir(dirNameWithPath)) + dirToDelete := filepath.Base(dirNameWithPath) + var totalPath string + if parentPath == utils.PathSeparator && filepath.ToSlash(dirToDelete) == utils.PathSeparator { + totalPath = utils.CombinePathAndFile(parentPath, "") + } else { + totalPath = utils.CombinePathAndFile(parentPath, dirToDelete) + } + err := d.file.RmFile(utils.CombinePathAndFile(totalPath, indexFileName), podPassword) + if err != nil { + return err + } + d.RemoveFromDirectoryMap(totalPath) + // return if root directory + if parentPath == "" || (parentPath == utils.PathSeparator && filepath.ToSlash(totalPath) == utils.PathSeparator) { return nil } - d.AddToDirectoryMap(dirNameWithPath, &inode) - return &inode + // remove the directory entry from the parent dir + return d.RemoveEntryFromDir(filepath.ToSlash(filepath.Dir(parentPath)), podPassword, filepath.Base(totalPath), false) } diff --git a/pkg/dir/ls.go b/pkg/dir/ls.go index b3b83f16..ba66cddb 100644 --- a/pkg/dir/ls.go +++ b/pkg/dir/ls.go @@ -47,21 +47,10 @@ type Entry struct { // function can give information about those files. func (d *Directory) ListDir(dirNameWithPath, podPassword string) ([]Entry, []string, error) { dirNameWithPath = filepath.ToSlash(dirNameWithPath) - topic := utils.HashString(dirNameWithPath) - _, data, err := d.fd.GetFeedData(topic, d.getAddress(), []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - if dirNameWithPath == utils.PathSeparator { - return nil, nil, nil - } - return nil, nil, fmt.Errorf("list dir : %v", err) // skipcq: TCV-001 - } - - dirInode := &Inode{} - err = dirInode.Unmarshal(data) + dirInode, err := d.GetInode(podPassword, dirNameWithPath) if err != nil { return nil, nil, fmt.Errorf("list dir : %v", err) } - wg := new(sync.WaitGroup) mtx := &sync.Mutex{} listEntries := &[]Entry{} diff --git a/pkg/dir/ls_test.go b/pkg/dir/ls_test.go index a7c00963..02e2ed0c 100644 --- a/pkg/dir/ls_test.go +++ b/pkg/dir/ls_test.go @@ -24,23 +24,35 @@ import ( "testing" "time" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "github.com/sirupsen/logrus" "github.com/plexsysio/taskmanager" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/fairdatasociety/fairOS-dfs/pkg/account" - bm "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" - fm "github.com/fairdatasociety/fairOS-dfs/pkg/file/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" ) func TestListDirectory(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -50,15 +62,15 @@ func TestListDirectory(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) - user := acc.GetAddress(1) - mockFile := fm.NewMockFile() - tm := taskmanager.New(1, 10, time.Second*15, logger) - defer func() { - _ = tm.Stop(context.Background()) - }() t.Run("list-dir", func(t *testing.T) { + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) + user := acc.GetAddress(1) + tm := taskmanager.New(1, 10, time.Second*15, logger) + defer func() { + _ = tm.Stop(context.Background()) + }() + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) podPassword, _ := utils.GetRandString(pod.PasswordLength) dirObject := dir.NewDirectory("pod1", mockClient, fd, user, mockFile, tm, logger) @@ -155,6 +167,13 @@ func TestListDirectory(t *testing.T) { }) t.Run("list-dir-from-different-dir-object", func(t *testing.T) { + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) + user := acc.GetAddress(1) + tm := taskmanager.New(1, 10, time.Second*15, logger) + defer func() { + _ = tm.Stop(context.Background()) + }() + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) podPassword, _ := utils.GetRandString(pod.PasswordLength) dirObject := dir.NewDirectory("pod1", mockClient, fd, user, mockFile, tm, logger) diff --git a/pkg/dir/mkdir.go b/pkg/dir/mkdir.go index f610730a..f4167de0 100644 --- a/pkg/dir/mkdir.go +++ b/pkg/dir/mkdir.go @@ -17,7 +17,6 @@ limitations under the License. package dir import ( - "encoding/json" "path/filepath" "strings" "time" @@ -49,14 +48,15 @@ func (d *Directory) MkDir(dirToCreateWithPath, podPassword string, mode uint32) // check if directory already present totalPath := utils.CombinePathAndFile(parentPath, dirName) - topic := utils.HashString(totalPath) // check if parent path exists - if d.GetInode(podPassword, parentPath) == nil { + _, err := d.GetInode(podPassword, parentPath) + if err != nil { return ErrDirectoryNotPresent } - if d.GetInode(podPassword, totalPath) != nil { + _, err = d.GetInode(podPassword, totalPath) + if err == nil { return ErrDirectoryAlreadyPresent } @@ -77,55 +77,13 @@ func (d *Directory) MkDir(dirToCreateWithPath, podPassword string, mode uint32) dirInode := &Inode{ Meta: &meta, } - data, err := json.Marshal(dirInode) - if err != nil { // skipcq: TCV-001 - return err - } - - // upload the metadata as blob - previousAddr, _, err := d.fd.GetFeedData(topic, d.userAddress, []byte(podPassword)) - if err == nil && previousAddr != nil { - _, err = d.fd.UpdateFeed(d.userAddress, topic, data, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - } else { - _, err = d.fd.CreateFeed(d.userAddress, topic, data, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - } - - d.AddToDirectoryMap(totalPath, dirInode) - - // get the parent directory entry and add this new directory to its list of children - parentHash := utils.HashString(utils.CombinePathAndFile(parentPath, "")) - dirName = "_D_" + dirName - _, parentData, err := d.fd.GetFeedData(parentHash, d.userAddress, []byte(podPassword)) - if err != nil { - return err - } - - // unmarshall the data and add the directory entry to the parent - var parentDirInode *Inode - err = json.Unmarshal(parentData, &parentDirInode) - if err != nil { // skipcq: TCV-001 - return err - } - parentDirInode.FileOrDirNames = append(parentDirInode.FileOrDirNames, dirName) - // marshall it back and update the parent feed - parentData, err = json.Marshal(parentDirInode) + err = d.SetInode(podPassword, dirInode) if err != nil { // skipcq: TCV-001 return err } - _, err = d.fd.UpdateFeed(d.userAddress, parentHash, parentData, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - d.AddToDirectoryMap(parentPath, parentDirInode) - return nil + return d.AddEntryToDir(parentPath, podPassword, dirName, false) } // MkRootDir creates the root directory for the pod @@ -143,42 +101,15 @@ func (d *Directory) MkRootDir(podName, podPassword string, podAddress utils.Addr parentDirInode := &Inode{ Meta: &meta, } - - parentData, err := json.Marshal(&parentDirInode) - if err != nil { // skipcq: TCV-001 - return err - } - parentPath := utils.CombinePathAndFile(utils.PathSeparator, "") - parentHash := utils.HashString(parentPath) - addr, data, err := d.fd.GetFeedData(parentHash, d.userAddress, []byte(podPassword)) - if err == nil && addr != nil && data != nil { - _, err = fd.UpdateFeed(podAddress, parentHash, parentData, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - } else { - _, err = fd.CreateFeed(podAddress, parentHash, parentData, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - } - d.AddToDirectoryMap(utils.PathSeparator, parentDirInode) - return nil + return d.SetInode(podPassword, parentDirInode) } // AddRootDir adds the root directory to the directory map func (d *Directory) AddRootDir(podName, podPassword string, podAddress utils.Address, fd *feed.API) error { - parentPath := utils.CombinePathAndFile(utils.PathSeparator, "") - parentHash := utils.HashString(parentPath) - _, parentDataBytes, err := fd.GetFeedData(parentHash, podAddress, []byte(podPassword)) + parentDirInode, err := d.GetInode(podPassword, utils.CombinePathAndFile(utils.PathSeparator, "")) if err != nil { return err } - var parentDirInode Inode - err = parentDirInode.Unmarshal(parentDataBytes) - if err != nil { - return err - } - d.AddToDirectoryMap(utils.PathSeparator, &parentDirInode) + d.AddToDirectoryMap(utils.PathSeparator, parentDirInode) return nil } diff --git a/pkg/dir/mkdir_test.go b/pkg/dir/mkdir_test.go index e3b25f67..15a49f6d 100644 --- a/pkg/dir/mkdir_test.go +++ b/pkg/dir/mkdir_test.go @@ -22,22 +22,34 @@ import ( "testing" "time" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/sirupsen/logrus" "github.com/plexsysio/taskmanager" "github.com/fairdatasociety/fairOS-dfs/pkg/account" - bm "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" - fm "github.com/fairdatasociety/fairOS-dfs/pkg/file/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" ) func TestMkdir(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -52,11 +64,12 @@ func TestMkdir(t *testing.T) { _ = tm.Stop(context.Background()) }() - fd := feed.New(pod1AccountInfo, mockClient, logger) user := acc.GetAddress(1) - mockFile := fm.NewMockFile() t.Run("simple-mkdir", func(t *testing.T) { + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) dirObject := dir.NewDirectory("pod1", mockClient, fd, user, mockFile, tm, logger) @@ -86,6 +99,9 @@ func TestMkdir(t *testing.T) { }) t.Run("complicated-mkdir", func(t *testing.T) { + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) dirObject := dir.NewDirectory("pod1", mockClient, fd, user, mockFile, tm, logger) diff --git a/pkg/dir/modify_dir_entry.go b/pkg/dir/modify_dir_entry.go index 9422fc56..ebceb3b5 100644 --- a/pkg/dir/modify_dir_entry.go +++ b/pkg/dir/modify_dir_entry.go @@ -17,12 +17,12 @@ limitations under the License. package dir import ( - "encoding/json" - "fmt" "path/filepath" "time" +) - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" +const ( + indexFileName = "index.dfs" ) // AddEntryToDir adds a new entry (directory/file) to a given directory. @@ -38,9 +38,9 @@ func (d *Directory) AddEntryToDir(parentDir, podPassword, itemToAdd string, isFi return ErrInvalidFileOrDirectoryName } - dirInode := d.GetInode(podPassword, parentDir) + dirInode, err := d.GetInode(podPassword, parentDir) // check if parent directory present - if dirInode == nil { + if err != nil { return ErrDirectoryNotPresent } @@ -53,19 +53,7 @@ func (d *Directory) AddEntryToDir(parentDir, podPassword, itemToAdd string, isFi dirInode.FileOrDirNames = append(dirInode.FileOrDirNames, itemToAdd) dirInode.Meta.ModificationTime = time.Now().Unix() - // update the feed of the dir and the data structure with the latest info - data, err := json.Marshal(dirInode) - if err != nil { // skipcq: TCV-001 - return fmt.Errorf("modify dir entry : %v", err) - } - - topic := utils.HashString(parentDir) - _, err = d.fd.UpdateFeed(d.userAddress, topic, data, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return fmt.Errorf("modify dir entry : %v", err) - } - d.AddToDirectoryMap(parentDir, dirInode) - return nil + return d.SetInode(podPassword, dirInode) } // RemoveEntryFromDir removes an entry (directory/file) under the given directory. @@ -81,15 +69,13 @@ func (d *Directory) RemoveEntryFromDir(parentDir, podPassword, itemToDelete stri return ErrInvalidFileOrDirectoryName } parentDir = filepath.ToSlash(parentDir) - parentDirInode := d.GetInode(podPassword, parentDir) + parentDirInode, err := d.GetInode(podPassword, parentDir) // check if parent directory present - if parentDirInode == nil { + if err != nil { d.logger.Errorf("remove entry from dir: parent directory not present %s\n", parentDir) return ErrDirectoryNotPresent } - parentHash := utils.HashString(parentDir) - if isFile { itemToDelete = "_F_" + itemToDelete } else { @@ -105,14 +91,5 @@ func (d *Directory) RemoveEntryFromDir(parentDir, podPassword, itemToDelete stri parentDirInode.FileOrDirNames = fileNames parentDirInode.Meta.ModificationTime = time.Now().Unix() - parentData, err := json.Marshal(parentDirInode) - if err != nil { // skipcq: TCV-001 - return err - } - _, err = d.fd.UpdateFeed(d.userAddress, parentHash, parentData, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - d.AddToDirectoryMap(parentDir, parentDirInode) - return nil + return d.SetInode(podPassword, parentDirInode) } diff --git a/pkg/dir/rename.go b/pkg/dir/rename.go index 27da0c90..d9b136dc 100644 --- a/pkg/dir/rename.go +++ b/pkg/dir/rename.go @@ -1,6 +1,7 @@ package dir import ( + "bufio" "encoding/json" "fmt" "path/filepath" @@ -36,33 +37,27 @@ func (d *Directory) RenameDir(dirNameWithPath, newDirNameWithPath, podPassword s } // check if directory exists - if d.GetInode(podPassword, dirNameWithPath) == nil { // skipcq: TCV-001 + _, err := d.GetInode(podPassword, dirNameWithPath) + if err != nil { // skipcq: TCV-001 return ErrDirectoryNotPresent } // check if parent directory exists - if d.GetInode(podPassword, parentPath) == nil { // skipcq: TCV-001 + _, err = d.GetInode(podPassword, parentPath) + if err != nil { // skipcq: TCV-001 return ErrDirectoryNotPresent } - if d.GetInode(podPassword, newDirNameWithPath) != nil { + _, err = d.GetInode(podPassword, newDirNameWithPath) + if err == nil { return ErrDirectoryAlreadyPresent } - err := d.mapChildrenToNewPath(dirNameWithPath, newDirNameWithPath, podPassword) + err = d.mapChildrenToNewPath(dirNameWithPath, newDirNameWithPath, podPassword) if err != nil { // skipcq: TCV-001 return err } - topic := utils.HashString(dirNameWithPath) - newTopic := utils.HashString(newDirNameWithPath) - _, inodeData, err := d.fd.GetFeedData(topic, d.userAddress, []byte(podPassword)) - if err != nil { - return err - } - - // unmarshall the data and rename the directory entry - var inode *Inode - err = json.Unmarshal(inodeData, &inode) + inode, err := d.GetInode(podPassword, dirNameWithPath) if err != nil { // skipcq: TCV-001 return err } @@ -77,25 +72,16 @@ func (d *Directory) RenameDir(dirNameWithPath, newDirNameWithPath, podPassword s return err } - previousAddr, _, err := d.fd.GetFeedData(newTopic, d.userAddress, []byte(podPassword)) - if err == nil && previousAddr != nil { - _, err = d.fd.UpdateFeed(d.userAddress, newTopic, fileMetaBytes, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - } else { - _, err = d.fd.CreateFeed(d.userAddress, newTopic, fileMetaBytes, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } + err = d.file.Upload(bufio.NewReader(strings.NewReader(string(fileMetaBytes))), indexFileName, int64(len(fileMetaBytes)), file.MinBlockSize, 0, newDirNameWithPath, "gzip", podPassword) + if err != nil { // skipcq: TCV-001 + return err } - // delete old meta - // update with utils.DeletedFeedMagicWord - _, err = d.fd.UpdateFeed(d.userAddress, topic, []byte(utils.DeletedFeedMagicWord), []byte(podPassword)) + err = d.file.RmFile(utils.CombinePathAndFile(dirNameWithPath, indexFileName), podPassword) if err != nil { // skipcq: TCV-001 return err } + d.RemoveFromDirectoryMap(dirNameWithPath) // get the parent directory entry and add this new directory to its list of children @@ -130,7 +116,7 @@ func (d *Directory) mapChildrenToNewPath(totalPath, newTotalPath, podPassword st filePath := utils.CombinePathAndFile(totalPath, fileName) newFilePath := utils.CombinePathAndFile(newTotalPath, fileName) topic := utils.HashString(filePath) - _, metaBytes, err := d.fd.GetFeedData(topic, d.userAddress, []byte(podPassword)) + _, metaBytes, err := d.fd.GetFeedData(topic, d.userAddress, []byte(podPassword), false) if err != nil { return err } @@ -153,14 +139,14 @@ func (d *Directory) mapChildrenToNewPath(totalPath, newTotalPath, podPassword st return err } - previousAddr, _, err := d.fd.GetFeedData(newTopic, d.userAddress, []byte(podPassword)) + previousAddr, _, err := d.fd.GetFeedData(newTopic, d.userAddress, []byte(podPassword), false) if err == nil && previousAddr != nil { - _, err = d.fd.UpdateFeed(d.userAddress, newTopic, fileMetaBytes, []byte(podPassword)) + err = d.fd.UpdateFeed(d.userAddress, newTopic, fileMetaBytes, []byte(podPassword), false) if err != nil { // skipcq: TCV-001 return err } } else { - _, err = d.fd.CreateFeed(d.userAddress, newTopic, fileMetaBytes, []byte(podPassword)) + err = d.fd.CreateFeed(d.userAddress, newTopic, fileMetaBytes, []byte(podPassword)) if err != nil { // skipcq: TCV-001 return err } @@ -168,7 +154,7 @@ func (d *Directory) mapChildrenToNewPath(totalPath, newTotalPath, podPassword st // delete old meta // update with utils.DeletedFeedMagicWord - _, err = d.fd.UpdateFeed(d.userAddress, topic, []byte(utils.DeletedFeedMagicWord), []byte(podPassword)) + err = d.fd.UpdateFeed(d.userAddress, topic, []byte(utils.DeletedFeedMagicWord), []byte(podPassword), false) if err != nil { // skipcq: TCV-001 return err } @@ -180,41 +166,22 @@ func (d *Directory) mapChildrenToNewPath(totalPath, newTotalPath, podPassword st if err != nil { // skipcq: TCV-001 return err } - topic := utils.HashString(pathWithDir) - newTopic := utils.HashString(newPathWithDir) - _, inodeData, err := d.fd.GetFeedData(topic, d.userAddress, []byte(podPassword)) - if err != nil { - return err - } - // unmarshall the data and add the directory entry to the parent - var inode *Inode - err = json.Unmarshal(inodeData, &inode) + + inode, err := d.GetInode(podPassword, pathWithDir) if err != nil { // skipcq: TCV-001 return err } + inode.Meta.Path = newTotalPath inode.Meta.ModificationTime = time.Now().Unix() - // upload meta - fileMetaBytes, err := json.Marshal(inode) + + err = d.SetInode(podPassword, inode) if err != nil { // skipcq: TCV-001 return err } - previousAddr, _, err := d.fd.GetFeedData(newTopic, d.userAddress, []byte(podPassword)) - if err == nil && previousAddr != nil { - _, err = d.fd.UpdateFeed(d.userAddress, newTopic, fileMetaBytes, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - } else { - _, err = d.fd.CreateFeed(d.userAddress, newTopic, fileMetaBytes, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - } // delete old meta - // update with utils.DeletedFeedMagicWord - _, err = d.fd.UpdateFeed(d.userAddress, topic, []byte(utils.DeletedFeedMagicWord), []byte(podPassword)) + err = d.RemoveInode(podPassword, pathWithDir) if err != nil { // skipcq: TCV-001 return err } diff --git a/pkg/dir/rename_test.go b/pkg/dir/rename_test.go index e45770f8..0c732f86 100644 --- a/pkg/dir/rename_test.go +++ b/pkg/dir/rename_test.go @@ -9,8 +9,11 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" - bm "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/file" @@ -18,11 +21,19 @@ import ( "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestRenameDirectory(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -32,7 +43,7 @@ func TestRenameDirectory(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { diff --git a/pkg/dir/rmdir.go b/pkg/dir/rmdir.go index 6b717851..1918a5f7 100644 --- a/pkg/dir/rmdir.go +++ b/pkg/dir/rmdir.go @@ -47,8 +47,8 @@ func (d *Directory) RmDir(directoryNameWithPath, podPassword string) error { totalPath = utils.CombinePathAndFile(parentPath, dirToDelete) } // recursive delete - dirInode := d.GetInode(podPassword, totalPath) - if dirInode == nil { + dirInode, err := d.GetInode(podPassword, totalPath) + if err != nil { return ErrDirectoryNotPresent } @@ -78,20 +78,7 @@ func (d *Directory) RmDir(directoryNameWithPath, podPassword string) error { } } - // remove the feed and clear the data structure - topic := utils.HashString(totalPath) - _, err := d.fd.UpdateFeed(d.userAddress, topic, []byte(utils.DeletedFeedMagicWord), []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - d.RemoveFromDirectoryMap(totalPath) - // return if root directory - if parentPath == utils.PathSeparator && filepath.ToSlash(dirToDelete) == utils.PathSeparator { - return nil - } - // remove the directory entry from the parent dir - - return d.RemoveEntryFromDir(parentPath, podPassword, dirToDelete, false) + return d.RemoveInode(podPassword, directoryNameWithPath) } // RmRootDir removes root directory and all the entries (file/directory) under that. @@ -104,7 +91,6 @@ func (d *Directory) RmRootDir(podPassword string) error { if d.GetDirFromDirectoryMap(totalPath) == nil { // skipcq: TCV-001 return ErrDirectoryNotPresent } - // recursive delete dirInode := d.GetDirFromDirectoryMap(totalPath) if dirInode.FileOrDirNames != nil && len(dirInode.FileOrDirNames) > 0 { @@ -132,14 +118,5 @@ func (d *Directory) RmRootDir(podPassword string) error { } } } - - // remove the feed and clear the data structure - topic := utils.HashString(totalPath) - _, err := d.fd.UpdateFeed(d.userAddress, topic, []byte(utils.DeletedFeedMagicWord), []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - d.RemoveFromDirectoryMap(totalPath) - - return nil + return d.RemoveInode(podPassword, dirToDelete) } diff --git a/pkg/dir/rmdir_test.go b/pkg/dir/rmdir_test.go index 1eb2482e..5390ec34 100644 --- a/pkg/dir/rmdir_test.go +++ b/pkg/dir/rmdir_test.go @@ -20,26 +20,37 @@ import ( "context" "errors" "io" - "strings" "testing" "time" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/sirupsen/logrus" "github.com/plexsysio/taskmanager" "github.com/fairdatasociety/fairOS-dfs/pkg/account" - bm "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" - fm "github.com/fairdatasociety/fairOS-dfs/pkg/file/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" ) func TestRmdir(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -54,9 +65,9 @@ func TestRmdir(t *testing.T) { _ = tm.Stop(context.Background()) }() - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) - mockFile := fm.NewMockFile() + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) t.Run("simple-rmdir", func(t *testing.T) { podPassword, _ := utils.GetRandString(pod.PasswordLength) @@ -168,8 +179,15 @@ func TestRmdir(t *testing.T) { } func TestRmRootDirByPath(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -179,13 +197,13 @@ func TestRmRootDirByPath(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) - mockFile := fm.NewMockFile() tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) t.Run("rmrootdir", func(t *testing.T) { podPassword, _ := utils.GetRandString(pod.PasswordLength) @@ -229,16 +247,11 @@ func TestRmRootDirByPath(t *testing.T) { t.Fatal("nested directory \"/dirToRemove1/dirToRemove2/dirToRemove\" was not created") } - fileName := "file1" - err = dirObject.AddEntryToDir("/dirToRemove1", podPassword, fileName, true) - if err != nil { - t.Fatal(err) - } _, fileEntry, err := dirObject.ListDir("/dirToRemove1", podPassword) if err != nil { t.Fatal(err) } - if len(fileEntry) != 1 { + if len(fileEntry) != 0 { t.Fatal("there should a file entry") } // now delete the root directory @@ -249,7 +262,7 @@ func TestRmRootDirByPath(t *testing.T) { // verify if the directory is actually removed dirEntry, _, err = dirObject.ListDir("/", podPassword) - if err != nil && !strings.HasSuffix(err.Error(), dir.ErrResourceDeleted.Error()) { + if err == nil { t.Fatal("root directory was not deleted") } if dirEntry != nil { @@ -259,8 +272,15 @@ func TestRmRootDirByPath(t *testing.T) { } func TestRmRootDir(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -275,9 +295,9 @@ func TestRmRootDir(t *testing.T) { _ = tm.Stop(context.Background()) }() - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) - mockFile := fm.NewMockFile() + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) t.Run("rmrootdir", func(t *testing.T) { podPassword, _ := utils.GetRandString(pod.PasswordLength) @@ -325,17 +345,12 @@ func TestRmRootDir(t *testing.T) { t.Fatal("nested directory \"/dirToRemove1/dirToRemove2/dirToRemove\" was not created") } - fileName := "file1" - err = dirObject.AddEntryToDir("/", podPassword, fileName, true) - if err != nil { - t.Fatal(err) - } _, fileEntry, err := dirObject.ListDir("/", podPassword) if err != nil { t.Fatal(err) } - if len(fileEntry) != 1 { - t.Fatal("there should a file entry") + if len(fileEntry) != 0 { + t.Fatal("there should no file entry") } // now delete the root directory @@ -346,7 +361,7 @@ func TestRmRootDir(t *testing.T) { // verify if the directory is actually removed dirEntry, _, err = dirObject.ListDir("/", podPassword) - if err != nil && !strings.HasSuffix(err.Error(), dir.ErrResourceDeleted.Error()) { + if err == nil { t.Fatal("root directory was not deleted") } if dirEntry != nil { diff --git a/pkg/dir/stat.go b/pkg/dir/stat.go index c7c37e63..db6d0cc9 100644 --- a/pkg/dir/stat.go +++ b/pkg/dir/stat.go @@ -17,12 +17,8 @@ limitations under the License. package dir import ( - "encoding/json" - "fmt" "strconv" "strings" - - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" ) // Stats represents a given directory @@ -40,19 +36,10 @@ type Stats struct { // DirStat returns all the information related to a given directory. func (d *Directory) DirStat(podName, podPassword, dirNameWithPath string) (*Stats, error) { - topic := utils.HashString(dirNameWithPath) - _, data, err := d.fd.GetFeedData(topic, d.getAddress(), []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return nil, fmt.Errorf("dir stat: %v", err) - } - if string(data) == utils.DeletedFeedMagicWord { - return nil, ErrDirectoryNotPresent - } - - var dirInode Inode - err = json.Unmarshal(data, &dirInode) + dirInode, err := d.GetInode(podPassword, dirNameWithPath) if err != nil { // skipcq: TCV-001 - return nil, fmt.Errorf("dir stat: %v", err) + d.logger.Errorf("dir stat : %v", err) + return nil, err } if dirInode.Meta == nil && dirInode.FileOrDirNames == nil { // skipcq: TCV-001 diff --git a/pkg/dir/stat_test.go b/pkg/dir/stat_test.go index c20a772c..99a7d494 100644 --- a/pkg/dir/stat_test.go +++ b/pkg/dir/stat_test.go @@ -24,22 +24,34 @@ import ( "testing" "time" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/sirupsen/logrus" "github.com/plexsysio/taskmanager" "github.com/fairdatasociety/fairOS-dfs/pkg/account" - bm "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" - fm "github.com/fairdatasociety/fairOS-dfs/pkg/file/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" ) func TestStat(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -49,13 +61,13 @@ func TestStat(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) - mockFile := fm.NewMockFile() tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) t.Run("stat-dir", func(t *testing.T) { podPassword, _ := utils.GetRandString(pod.PasswordLength) @@ -80,16 +92,6 @@ func TestStat(t *testing.T) { if err != nil { t.Fatal(err) } - // just add dummy file entry as file listing is not tested here - err = dirObject.AddEntryToDir("/dirToStat", podPassword, "file1", true) - if err != nil { - t.Fatal(err) - } - err = dirObject.AddEntryToDir("/dirToStat", podPassword, "file2", true) - if err != nil { - t.Fatal(err) - } - // stat the directory dirStats, err := dirObject.DirStat("pod1", podPassword, "/dirToStat") if err != nil { @@ -112,7 +114,7 @@ func TestStat(t *testing.T) { if dirStats.NoOfDirectories != strconv.FormatUint(2, 10) { t.Fatalf("invalid directory count") } - if dirStats.NoOfFiles != strconv.FormatUint(2, 10) { + if dirStats.NoOfFiles != strconv.FormatUint(0, 10) { t.Fatalf("invalid files count") } @@ -123,7 +125,7 @@ func TestStat(t *testing.T) { _, err = dirObject.DirStat("pod1", podPassword, "/dirToStat") if !errors.Is(err, dir.ErrDirectoryNotPresent) { - t.Fatal("dir should not be present") + t.Fatal("dir should not be present", err) } }) } diff --git a/pkg/dir/sync.go b/pkg/dir/sync.go index dad0186e..b2a230b1 100644 --- a/pkg/dir/sync.go +++ b/pkg/dir/sync.go @@ -26,19 +26,12 @@ import ( // SyncDirectory syncs all the latest entries under a given directory. func (d *Directory) SyncDirectory(dirNameWithPath, podPassword string) error { - topic := utils.HashString(utils.CombinePathAndFile(dirNameWithPath, "")) - _, data, err := d.fd.GetFeedData(topic, d.userAddress, []byte(podPassword)) + dirInode, err := d.GetInode(podPassword, dirNameWithPath) if err != nil { // skipcq: TCV-001 return nil // pod is empty } - var dirInode Inode - err = dirInode.Unmarshal(data) - if err != nil { // skipcq: TCV-001 - d.logger.Errorf("dir sync: %v", err) - return err - } - d.AddToDirectoryMap(dirNameWithPath, &dirInode) + d.AddToDirectoryMap(dirNameWithPath, dirInode) for _, fileOrDirName := range dirInode.FileOrDirNames { if strings.HasPrefix(fileOrDirName, "_F_") { fileName := strings.TrimPrefix(fileOrDirName, "_F_") @@ -63,20 +56,11 @@ func (d *Directory) SyncDirectory(dirNameWithPath, podPassword string) error { // SyncDirectoryAsync syncs all the latest entries under a given directory concurrently. func (d *Directory) SyncDirectoryAsync(ctx context.Context, dirNameWithPath, podPassword string, wg *sync.WaitGroup) error { - topic := utils.HashString(utils.CombinePathAndFile(dirNameWithPath, "")) - _, data, err := d.fd.GetFeedData(topic, d.userAddress, []byte(podPassword)) + dirInode, err := d.GetInode(podPassword, dirNameWithPath) if err != nil { // skipcq: TCV-001 return nil // pod is empty } - var dirInode Inode - err = dirInode.Unmarshal(data) - if err != nil { // skipcq: TCV-001 - d.logger.Errorf("dir sync: %v", err) - return err - } - - d.AddToDirectoryMap(dirNameWithPath, &dirInode) for _, fileOrDirName := range dirInode.FileOrDirNames { if strings.HasPrefix(fileOrDirName, "_F_") { wg.Add(1) diff --git a/pkg/dir/sync_test.go b/pkg/dir/sync_test.go index 6a0b24b2..07561b51 100644 --- a/pkg/dir/sync_test.go +++ b/pkg/dir/sync_test.go @@ -23,15 +23,20 @@ import ( "testing" "time" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" - bm "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" - fm "github.com/fairdatasociety/fairOS-dfs/pkg/file/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" "go.uber.org/goleak" ) @@ -40,8 +45,15 @@ func TestMain(m *testing.M) { } func TestSync(t *testing.T) { - mockClient := bm.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -51,13 +63,13 @@ func TestSync(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) - mockFile := fm.NewMockFile() tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() + mockFile := file.NewFile("pod1", mockClient, fd, user, tm, logger) t.Run("sync-dir", func(t *testing.T) { podPassword, _ := utils.GetRandString(pod.PasswordLength) @@ -82,15 +94,7 @@ func TestSync(t *testing.T) { if err != nil { t.Fatal(err) } - // just add dummy file entry as file listing is not tested here - err = dirObject.AddEntryToDir("/dirToStat", podPassword, "file1", true) - if err != nil { - t.Fatal(err) - } - err = dirObject.AddEntryToDir("/dirToStat", podPassword, "file2", true) - if err != nil { - t.Fatal(err) - } + dirObject2 := dir.NewDirectory("pod1", mockClient, fd, user, mockFile, tm, logger) if dirObject2.GetDirFromDirectoryMap("/") != nil { t.Fatal("it should be nil before sync") diff --git a/pkg/ensm/eth/eth.go b/pkg/ensm/eth/eth.go index 11fa5a3e..d1ab0535 100644 --- a/pkg/ensm/eth/eth.go +++ b/pkg/ensm/eth/eth.go @@ -61,7 +61,6 @@ func New(ensConfig *contracts.ENSConfig, logger logging.Logger) (*Client, error) return nil, fmt.Errorf("dial eth ensm: %w", err) } if chainID.String() != ensConfig.ChainID { - fmt.Println("chainID does not match or not supported", chainID.String(), ensConfig.ChainID) return nil, ErrWrongChainID } ensRegistry, err := ens.NewENSRegistry(common.HexToAddress(ensConfig.ENSRegistryAddress), eth) diff --git a/pkg/feed/api.go b/pkg/feed/api.go index 4e2c3b2f..3584106b 100644 --- a/pkg/feed/api.go +++ b/pkg/feed/api.go @@ -19,11 +19,9 @@ package feed import ( "context" "fmt" - "strings" "time" "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/soc" "github.com/ethersphere/bee/pkg/swarm" bmtlegacy "github.com/ethersphere/bmt/legacy" "github.com/fairdatasociety/fairOS-dfs/pkg/account" @@ -71,92 +69,63 @@ type request struct { } // New create the main feed object which is used to create/update/delete feeds. -func New(accountInfo *account.Info, client blockstore.Client, logger logging.Logger) *API { +func New(accountInfo *account.Info, client blockstore.Client, feedCacheSize int, feedCacheTTL time.Duration, logger logging.Logger) *API { bmtPool := bmtlegacy.NewTreePool(hashFunc, swarm.Branches, bmtlegacy.PoolSize) return &API{ - handler: NewHandler(accountInfo, client, bmtPool), + handler: NewHandler(accountInfo, client, bmtPool, feedCacheSize, feedCacheTTL, logger), accountInfo: accountInfo, logger: logger, } } +func (a *API) CommitFeeds() { + a.handler.commit() +} + // CreateFeed creates a feed by constructing a single owner chunk. This chunk // can only be accessed if the pod address is known. Also, no one else can spoof this // chunk since this is signed by the pod. -func (a *API) CreateFeed(user utils.Address, topic, data, encryptionPassword []byte) ([]byte, error) { - var req request +func (a *API) CreateFeed(user utils.Address, topic, data, encryptionPassword []byte) error { if a.accountInfo.GetPrivateKey() == nil { - return nil, ErrReadOnlyFeed + return ErrReadOnlyFeed } if len(topic) != TopicLength { - return nil, ErrInvalidTopicSize + return ErrInvalidTopicSize } if len(data) > utils.MaxChunkLength { - return nil, ErrInvalidPayloadSize + return ErrInvalidPayloadSize } var err error encryptedData := data - if encryptionPassword != nil { // skipcq: TCV-001 + if len(encryptionPassword) != 0 { // skipcq: TCV-001 encryptedData, err = utils.EncryptBytes(encryptionPassword, data) if err != nil { // skipcq: TCV-001 - return nil, err + return err } } - // fill Feed and Epoc related details - copy(req.ID.Topic[:], topic) - req.ID.User = user - req.Epoch.Level = 31 - req.Epoch.Time = uint64(time.Now().Unix()) - - // Add initial feed data - req.data = encryptedData - // create the id, hash(topic, epoc) - id, err := a.handler.getId(req.Topic, req.Time, req.Level) - if err != nil { // skipcq: TCV-001 - return nil, err - } - // get the payload id BMT(span, payload) - payloadId, err := a.handler.getPayloadId(encryptedData) - if err != nil { // skipcq: TCV-001 - return nil, err - } - // create the signer and the content addressed chunk - signer := crypto.NewDefaultSigner(a.accountInfo.GetPrivateKey()) - ch, err := utils.NewChunkWithSpan(encryptedData) - if err != nil { // skipcq: TCV-001 - return nil, err - } - s := soc.New(id, ch) - sch, err := s.Sign(signer) - if err != nil { // skipcq: TCV-001 - return nil, err - } - // generate the data to sign - toSignBytes, err := toSignDigest(id, ch.Address().Bytes()) - if err != nil { // skipcq: TCV-001 - return nil, err - } - // sign the chunk - signature, err := signer.Sign(toSignBytes) - if err != nil { // skipcq: TCV-001 - return nil, err + if a.handler.pool != nil { + item := &feedItem{ + User: user, + AccountInfo: a.accountInfo, + Topic: topic, + Data: encryptedData, + ShouldCreate: true, + } + a.handler.putInPool(topic, item) + return nil } - // set the address and the data for the soc chunk - req.idAddr = sch.Address() - req.binaryData = sch.Data() - // set signature and binary data fields - _, err = a.handler.toChunkContent(&req, id, payloadId) - if err != nil { // skipcq: TCV-001 - return nil, err + _, _, err = a.handler.createSoc(user, a.accountInfo, topic, encryptedData) + if err != nil { + a.handler.logger.Errorf("failed to createSoc: %v\n", err) + return err } - // send the updated soc chunk to bee - return a.handler.update(id, user.ToBytes(), signature, ch.Data()) + return nil } // CreateFeedFromTopic creates a soc with the topic as identifier @@ -214,35 +183,25 @@ func (a *API) GetSOCFromAddress(address []byte) ([]byte, error) { } // GetFeedData looks up feed from swarm -func (a *API) GetFeedData(topic []byte, user utils.Address, encryptionPassword []byte) ([]byte, []byte, error) { +func (a *API) GetFeedData(topic []byte, user utils.Address, encryptionPassword []byte, isFeedUpdater bool) ([]byte, []byte, error) { if len(topic) != TopicLength { return nil, nil, ErrInvalidTopicSize } - ctx := context.Background() - f := new(Feed) - f.User = user - copy(f.Topic[:], topic) - // create the query from values - q := &Query{Feed: *f} - q.TimeLimit = 0 - q.Hint = lookup.NoClue - _, err := a.handler.Lookup(ctx, q) + hint := lookup.NoClue + + addr, data, err := a.handler.getSoc(topic, user, hint) if err != nil { return nil, nil, err } - addr, encryptedData, err := a.handler.GetContent(&q.Feed) - if err != nil { // skipcq: TCV-001 - return nil, nil, err - } - if encryptionPassword == nil || string(encryptedData) == utils.DeletedFeedMagicWord { - return addr.Bytes(), encryptedData, nil + if len(encryptionPassword) == 0 || string(data) == utils.DeletedFeedMagicWord { + return addr, data, nil } - data, err := utils.DecryptBytes(encryptionPassword, encryptedData) + decryptedData, err := utils.DecryptBytes(encryptionPassword, data) if err != nil { // skipcq: TCV-001 return nil, nil, err } - return addr.Bytes(), data, nil + return addr, decryptedData, nil } // GetFeedDataFromTopic will generate keccak256 reference of the topic+address and download soc @@ -271,96 +230,45 @@ func (a *API) GetFeedDataFromTopic(topic []byte, user utils.Address) ([]byte, [] } // UpdateFeed updates the contents of an already created feed. -func (a *API) UpdateFeed(user utils.Address, topic, data, encryptionPassword []byte) ([]byte, error) { +func (a *API) UpdateFeed(user utils.Address, topic, data, encryptionPassword []byte, isFeedUpdater bool) error { if a.accountInfo.GetPrivateKey() == nil { - return nil, ErrReadOnlyFeed + return ErrReadOnlyFeed } if len(topic) != TopicLength { - return nil, ErrInvalidTopicSize + return ErrInvalidTopicSize } if len(data) > utils.MaxChunkLength { - return nil, ErrInvalidPayloadSize + return ErrInvalidPayloadSize } var err error encryptedData := data - if encryptionPassword != nil && string(data) != utils.DeletedFeedMagicWord { + if len(encryptionPassword) != 0 && string(data) != utils.DeletedFeedMagicWord { encryptedData, err = utils.EncryptBytes(encryptionPassword, data) if err != nil { // skipcq: TCV-001 - return nil, err + return err } } - retries := 0 -retry: - ctx := context.Background() - f := new(Feed) - f.User = user - copy(f.Topic[:], topic) - - // get the existing request from DB - req, err := a.handler.newRequest(ctx, f) - if err != nil { // skipcq: TCV-001 - return nil, err - } - req.Time = uint64(time.Now().Unix()) - req.data = encryptedData - // create the id, hash(topic, epoc) - id, err := a.handler.getId(req.Topic, req.Time, req.Level) - if err != nil { // skipcq: TCV-001 - return nil, err - } - // get the payload id BMT(span, payload) - payloadId, err := a.handler.getPayloadId(encryptedData) - if err != nil { // skipcq: TCV-001 - return nil, err - } - // create the signer and the content addressed chunk - signer := crypto.NewDefaultSigner(a.accountInfo.GetPrivateKey()) - ch, err := utils.NewChunkWithSpan(encryptedData) - if err != nil { // skipcq: TCV-001 - return nil, err - } - s := soc.New(id, ch) - sch, err := s.Sign(signer) - if err != nil { // skipcq: TCV-001 - return nil, err - } - // generate the data to sign - toSignBytes, err := toSignDigest(id, ch.Address().Bytes()) - if err != nil { // skipcq: TCV-001 - return nil, err - } - // sign the chunk - signature, err := signer.Sign(toSignBytes) - if err != nil { // skipcq: TCV-001 - return nil, err - } - // set the address and the data for the soc chunk - req.idAddr = sch.Address() - req.binaryData = sch.Data() - // set signature and binary data fields - _, err = a.handler.toChunkContent(req, id, payloadId) - if err != nil { // skipcq: TCV-001 - return nil, err + if a.handler.pool != nil { + item := &feedItem{ + User: user, + AccountInfo: a.accountInfo, + Topic: topic, + Data: encryptedData, + ShouldCreate: false, + } + a.handler.putInPool(topic, item) + return nil } - - address, err := a.handler.update(id, user.ToBytes(), signature, ch.Data()) + _, _, err = a.handler.updateSoc(user, a.accountInfo, topic, encryptedData) if err != nil { - // updating same feed in the same second will lead to "chunk already exists" error. - // This will wait for 1 second and retry the update maxUpdateRetry times. - // It is a very dirty fix for this issue. We should find a better way to handle this. - if strings.Contains(err.Error(), "chunk already exists") && retries < maxUpdateRetry { - retries++ - <-time.After(1 * time.Second) - goto retry - } - return nil, err + a.handler.logger.Errorf("failed to updateSoc: %v\n", err) + return err } - - return address, nil + return nil } // DeleteFeed deleted the feed by updating with no data inside the SOC chunk. @@ -369,7 +277,7 @@ func (a *API) DeleteFeed(topic []byte, user utils.Address) error { return ErrReadOnlyFeed } - delRef, _, err := a.GetFeedData(topic, user, nil) + delRef, _, err := a.GetFeedData(topic, user, nil, false) if err != nil && err.Error() != "feed does not exist or was not updated yet" { // skipcq: TCV-001 return err } @@ -407,3 +315,8 @@ func (a *API) DeleteFeedFromTopic(topic []byte, user utils.Address) error { func (a *API) IsReadOnlyFeed() bool { return a.accountInfo.GetPrivateKey() == nil } + +func (a *API) Close() error { + a.CommitFeeds() + return nil +} diff --git a/pkg/feed/feed_test.go b/pkg/feed/feed_test.go index 27f4cb5c..e64db33b 100644 --- a/pkg/feed/feed_test.go +++ b/pkg/feed/feed_test.go @@ -18,13 +18,18 @@ package feed_test import ( "bytes" - "encoding/binary" + "crypto/rand" "errors" - "fmt" "io" "testing" + "time" + "github.com/stretchr/testify/require" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" @@ -39,87 +44,174 @@ func TestMain(m *testing.M) { func TestFeed(t *testing.T) { logger := logging.New(io.Discard, 0) - acc1 := account.New(logger) - _, _, err := acc1.CreateUserAccount("") - if err != nil { - t.Fatal(err) - } - user1 := acc1.GetAddress(account.UserAccountIndex) - accountInfo1 := acc1.GetUserAccountInfo() - client := mock.NewMockBeeClient() + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + client := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) t.Run("create-feed", func(t *testing.T) { - fd := feed.New(accountInfo1, client, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + fd := feed.New(accountInfo, client, 500, 0, logger) topic := utils.HashString("topic1") data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - addr, err := fd.CreateFeed(user1, topic, data, nil) + err = fd.CreateFeed(user, topic, data, nil) if err != nil { t.Fatal(err) } longTopic := append(topic, topic...) // skipcq: CRT-D0001 - _, _, err = fd.GetFeedData(longTopic, user1, nil) + _, _, err = fd.GetFeedData(longTopic, user, nil, false) if !errors.Is(err, feed.ErrInvalidTopicSize) { t.Fatal("invalid topic size") } - + <-time.After(3 * time.Second) // check if the data and address is present and is same as stored - rcvdAddr, rcvdData, err := fd.GetFeedData(topic, user1, nil) + _, rcvdData, err := fd.GetFeedData(topic, user, nil, false) if err != nil { t.Fatal(err) } - if !bytes.Equal(addr, rcvdAddr) { + + if !bytes.Equal(data, rcvdData) { + t.Fatal(err) + } + }) + + t.Run("create-feed-nil-feed-cache", func(t *testing.T) { + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + fd := feed.New(accountInfo, client, -1, 0, logger) + topic := utils.HashString("topic1") + data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + err = fd.CreateFeed(user, topic, data, nil) + if err != nil { + t.Fatal(err) + } + longTopic := append(topic, topic...) // skipcq: CRT-D0001 + _, _, err = fd.GetFeedData(longTopic, user, nil, false) + if !errors.Is(err, feed.ErrInvalidTopicSize) { + t.Fatal("invalid topic size") + } + <-time.After(3 * time.Second) + // check if the data and address is present and is same as stored + _, rcvdData, err := fd.GetFeedData(topic, user, nil, false) + if err != nil { t.Fatal(err) } + if !bytes.Equal(data, rcvdData) { t.Fatal(err) } }) - t.Run("create-from-user1-read-from-user2-with-user1-address", func(t *testing.T) { - // create account2 - acc2 := account.New(logger) - _, _, err = acc2.CreateUserAccount("") + t.Run("create-from-user-read-from-user2-with-user-address", func(t *testing.T) { + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - accountInfo2 := acc2.GetUserAccountInfo() + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() - // create feed from user1 - fd1 := feed.New(accountInfo1, client, logger) + // create feed from user + fd1 := feed.New(accountInfo, client, 500, 0, logger) topic := utils.HashString("topic1") data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - addr, err := fd1.CreateFeed(user1, topic, data, nil) + err = fd1.CreateFeed(user, topic, data, nil) if err != nil { t.Fatal(err) } - + fd1.CommitFeeds() // check if you can read the data from user2 - fd2 := feed.New(accountInfo2, client, logger) - rcvdAddr, rcvdData, err := fd2.GetFeedData(topic, user1, nil) + fd2 := feed.New(accountInfo, client, -1, 0, logger) + _, rcvdData, err := fd2.GetFeedData(topic, user, nil, false) if err != nil { t.Fatal(err) } - if !bytes.Equal(addr, rcvdAddr) { - t.Fatal("addresses do not match") + + if !bytes.Equal(data, rcvdData) { + t.Fatal("data does not match") } + }) + + t.Run("create-from-user-read-from-user2-with-user-address-nil-feed-cache", func(t *testing.T) { + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + + // create feed from user + fd1 := feed.New(accountInfo, client, -1, 0, logger) + topic := utils.HashString("topic1") + data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + err = fd1.CreateFeed(user, topic, data, nil) + if err != nil { + t.Fatal(err) + } + fd1.CommitFeeds() + // check if you can read the data from user2 + fd2 := feed.New(accountInfo, client, -1, 0, logger) + _, rcvdData, err := fd2.GetFeedData(topic, user, nil, false) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(data, rcvdData) { t.Fatal("data does not match") } }) t.Run("read-feed-first-time", func(t *testing.T) { - fd := feed.New(accountInfo1, client, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + + fd := feed.New(accountInfo, client, -1, 0, logger) topic := utils.HashString("topic2") // check if the data and address is present and is same as stored - _, _, err := fd.GetFeedData(topic, user1, nil) + _, _, err = fd.GetFeedData(topic, user, nil, false) if err != nil && err.Error() != "feed does not exist or was not updated yet" { t.Fatal(err) } }) - t.Run("create-from-user1-read-from-user2-with-user2-address", func(t *testing.T) { - // create account2 + t.Run("read-feed-created-from-different-user", func(t *testing.T) { + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + accountInfo := acc.GetUserAccountInfo() + user := acc.GetAddress(account.UserAccountIndex) + + // create feed from user + fd1 := feed.New(accountInfo, client, 500, 0, logger) + topic := utils.HashString("topic1") + data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + err = fd1.CreateFeed(user, topic, data, nil) + if err != nil { + t.Fatal(err) + } + acc2 := account.New(logger) _, _, err = acc2.CreateUserAccount("") if err != nil { @@ -128,73 +220,215 @@ func TestFeed(t *testing.T) { accountInfo2 := acc2.GetUserAccountInfo() user2 := acc2.GetAddress(account.UserAccountIndex) - // create feed from user1 - fd1 := feed.New(accountInfo1, client, logger) + // check if you can read the data from user2 + fd2 := feed.New(accountInfo2, client, -1, 0, logger) + rcvdAddr, rcvdData, err := fd2.GetFeedData(topic, user2, nil, false) + if err != nil && err.Error() != "feed does not exist or was not updated yet" { + t.Fatal(err) + } + if rcvdAddr != nil || rcvdData != nil { + t.Fatal("was able to read feed of user using user2's address") + } + }) + + t.Run("read-feed-created-from-different-user-nil-feed-cache", func(t *testing.T) { + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + accountInfo := acc.GetUserAccountInfo() + user := acc.GetAddress(account.UserAccountIndex) + + // create feed from user + fd1 := feed.New(accountInfo, client, -1, 0, logger) topic := utils.HashString("topic1") data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - _, err := fd1.CreateFeed(user1, topic, data, nil) + err = fd1.CreateFeed(user, topic, data, nil) + if err != nil { + t.Fatal(err) + } + + acc2 := account.New(logger) + _, _, err = acc2.CreateUserAccount("") if err != nil { t.Fatal(err) } + accountInfo2 := acc2.GetUserAccountInfo() + user2 := acc2.GetAddress(account.UserAccountIndex) // check if you can read the data from user2 - fd2 := feed.New(accountInfo2, client, logger) - rcvdAddr, rcvdData, err := fd2.GetFeedData(topic, user2, nil) + fd2 := feed.New(accountInfo2, client, -1, 0, logger) + rcvdAddr, rcvdData, err := fd2.GetFeedData(topic, user2, nil, false) if err != nil && err.Error() != "feed does not exist or was not updated yet" { t.Fatal(err) } if rcvdAddr != nil || rcvdData != nil { - t.Fatal("was able to read feed of user1 using user2's address") + t.Fatal("was able to read feed of user using user2's address") } }) t.Run("update-feed", func(t *testing.T) { - fd := feed.New(accountInfo1, client, logger) + + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + fd := feed.New(accountInfo, client, 500, 0, logger) topic := utils.HashString("topic3") data := []byte{0} - _, err = fd.CreateFeed(user1, topic, data, nil) + err = fd.CreateFeed(user, topic, data, nil) if err != nil { t.Fatal(err) } + var finalData []byte for i := 1; i < 256; i++ { buf := make([]byte, 4) - binary.LittleEndian.PutUint16(buf, uint16(i)) + _, _ = rand.Read(buf) + if i == 255 { + finalData = buf + } + err = fd.UpdateFeed(user, topic, buf, nil, false) + if err != nil { + t.Fatal(err) + } + _, rcvdData, err := fd.GetFeedData(topic, user, nil, false) + if err != nil { + t.Fatal(err) + } - _, err = fd.UpdateFeed(user1, topic, buf, nil) + require.Equal(t, buf, rcvdData) + } + + fd.CommitFeeds() + <-time.After(time.Second) + _, rcvdData, err := fd.GetFeedData(topic, user, nil, false) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, finalData, rcvdData) + }) + + t.Run("update-feed-read-from-different-user", func(t *testing.T) { + + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + fd := feed.New(accountInfo, client, 500, 0, logger) + + topic := utils.HashString("topic3") + data := []byte{0} + err = fd.CreateFeed(user, topic, data, nil) + if err != nil { + t.Fatal(err) + } + + for i := 1; i < 256; i++ { + buf := make([]byte, 4) + _, _ = rand.Read(buf) + err = fd.UpdateFeed(user, topic, buf, nil, false) if err != nil { t.Fatal(err) } - getAddr, rcvdData, err := fd.GetFeedData(topic, user1, nil) + fd.CommitFeeds() + <-time.After(time.Second) + acc2 := account.New(logger) + _, _, err = acc2.CreateUserAccount("") if err != nil { t.Fatal(err) } - if getAddr == nil { - t.Fatal("invalid update address") + accountInfo2 := acc2.GetUserAccountInfo() + + // check if you can read the data from user2 + fd2 := feed.New(accountInfo2, client, -1, 0, logger) + _, rcvdData2, err := fd2.GetFeedData(topic, user, nil, false) + if err != nil && err.Error() != "feed does not exist or was not updated yet" { + t.Fatal(err) + } + + require.Equal(t, buf, rcvdData2) + } + }) + + t.Run("update-feed-nil-feed-cache", func(t *testing.T) { + + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + fd := feed.New(accountInfo, client, 500, 0, logger) + topic := utils.HashString("topic3") + data := []byte{0} + err = fd.CreateFeed(user, topic, data, nil) + if err != nil { + t.Fatal(err) + } + var finalData []byte + + for i := 1; i < 256; i++ { + buf := make([]byte, 4) + _, _ = rand.Read(buf) + if i == 255 { + finalData = buf + } + err = fd.UpdateFeed(user, topic, buf, nil, false) + if err != nil { + t.Fatal(err) } - if !bytes.Equal(buf, rcvdData) { - t.Fatal("data not matching", buf, rcvdData) + _, rcvdData, err := fd.GetFeedData(topic, user, nil, false) + if err != nil { + t.Fatal(err) } - fmt.Println("update ", i, " Done") + + require.Equal(t, buf, rcvdData) + <-time.After(time.Second) } + + fd.CommitFeeds() + <-time.After(time.Second) + _, rcvdData, err := fd.GetFeedData(topic, user, nil, false) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, finalData, rcvdData) }) t.Run("create-feed-from-topic", func(t *testing.T) { - fd := feed.New(accountInfo1, client, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + fd := feed.New(accountInfo, client, -1, 0, logger) topic := utils.HashString("feed-topic1") data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - addr, err := fd.CreateFeedFromTopic(topic, user1, data) + addr, err := fd.CreateFeedFromTopic(topic, user, data) if err != nil { t.Fatal(err) } longTopic := append(topic, topic...) // skipcq: CRT-D0001 - _, _, err = fd.GetFeedDataFromTopic(longTopic, user1) + _, _, err = fd.GetFeedDataFromTopic(longTopic, user) if !errors.Is(err, feed.ErrInvalidTopicSize) { t.Fatal("invalid topic size") } // check if the data and address is present and is same as stored - rcvdAddr, rcvdData, err := fd.GetFeedDataFromTopic(topic, user1) + rcvdAddr, rcvdData, err := fd.GetFeedDataFromTopic(topic, user) if err != nil { t.Fatal(err) } @@ -207,38 +441,52 @@ func TestFeed(t *testing.T) { }) t.Run("delete-feed-from-topic", func(t *testing.T) { - fd := feed.New(accountInfo1, client, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + fd := feed.New(accountInfo, client, -1, 0, logger) topic := utils.HashString("feed-topic1") data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - _, err := fd.CreateFeedFromTopic(topic, user1, data) + _, err = fd.CreateFeedFromTopic(topic, user, data) if err != nil { t.Fatal(err) } - err = fd.DeleteFeedFromTopic(topic, user1) + err = fd.DeleteFeedFromTopic(topic, user) if err != nil { t.Fatal(err) } - _, _, err = fd.GetFeedDataFromTopic(topic, user1) + _, _, err = fd.GetFeedDataFromTopic(topic, user) if err != nil && err.Error() != "error downloading data" { t.Fatal("error should be \"error downloading data\"") } }) t.Run("create-feed-errors", func(t *testing.T) { - nilFd := feed.New(&account.Info{}, client, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + nilFd := feed.New(&account.Info{}, client, -1, 0, logger) - fd := feed.New(accountInfo1, client, logger) + fd := feed.New(accountInfo, client, -1, 0, logger) topic := utils.HashString("feed-topic1") data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - _, err = nilFd.CreateFeed(user1, topic, data, nil) + err = nilFd.CreateFeed(user, topic, data, nil) if !errors.Is(err, feed.ErrReadOnlyFeed) { t.Fatal("read only feed") } longTopic := append(topic, topic...) // skipcq: CRT-D0001 - _, err = fd.CreateFeed(user1, longTopic, data, nil) + err = fd.CreateFeed(user, longTopic, data, nil) if !errors.Is(err, feed.ErrInvalidTopicSize) { t.Fatal("invalid topic size") } @@ -247,26 +495,32 @@ func TestFeed(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = fd.CreateFeed(user1, topic, longData, nil) + err = fd.CreateFeed(user, topic, longData, nil) if !errors.Is(err, feed.ErrInvalidPayloadSize) { t.Fatal("invalid payload size") } }) t.Run("create-feed-from-topic-errors", func(t *testing.T) { - nilFd := feed.New(&account.Info{}, client, logger) - - fd := feed.New(accountInfo1, client, logger) + nilFd := feed.New(&account.Info{}, client, -1, 0, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + fd := feed.New(accountInfo, client, -1, 0, logger) topic := utils.HashString("feed-topic1") data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - _, err = nilFd.CreateFeedFromTopic(topic, user1, data) + _, err = nilFd.CreateFeedFromTopic(topic, user, data) if !errors.Is(err, feed.ErrReadOnlyFeed) { t.Fatal("read only feed") } longTopic := append(topic, topic...) // skipcq: CRT-D0001 - _, err = fd.CreateFeedFromTopic(longTopic, user1, data) + _, err = fd.CreateFeedFromTopic(longTopic, user, data) if !errors.Is(err, feed.ErrInvalidTopicSize) { t.Fatal("invalid topic size") } @@ -275,26 +529,32 @@ func TestFeed(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = fd.CreateFeedFromTopic(topic, user1, longData) + _, err = fd.CreateFeedFromTopic(topic, user, longData) if !errors.Is(err, feed.ErrInvalidPayloadSize) { t.Fatal("invalid payload size") } }) t.Run("feed-update-errors", func(t *testing.T) { - nilFd := feed.New(&account.Info{}, client, logger) - - fd := feed.New(accountInfo1, client, logger) + nilFd := feed.New(&account.Info{}, client, -1, 0, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + fd := feed.New(accountInfo, client, -1, 0, logger) topic := utils.HashString("feed-topic1") data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - _, err = nilFd.UpdateFeed(user1, topic, data, nil) + err = nilFd.UpdateFeed(user, topic, data, nil, false) if !errors.Is(err, feed.ErrReadOnlyFeed) { t.Fatal("read only feed") } longTopic := append(topic, topic...) // skipcq: CRT-D0001 - _, err = fd.UpdateFeed(user1, longTopic, data, nil) + err = fd.UpdateFeed(user, longTopic, data, nil, false) if !errors.Is(err, feed.ErrInvalidTopicSize) { t.Fatal("invalid topic size") } @@ -303,39 +563,51 @@ func TestFeed(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = fd.UpdateFeed(user1, topic, longData, nil) + err = fd.UpdateFeed(user, topic, longData, nil, false) if !errors.Is(err, feed.ErrInvalidPayloadSize) { t.Fatal("invalid payload size") } }) t.Run("feed-delete-errors", func(t *testing.T) { - nilFd := feed.New(&account.Info{}, client, logger) - - fd := feed.New(accountInfo1, client, logger) + nilFd := feed.New(&account.Info{}, client, -1, 0, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) + accountInfo := acc.GetUserAccountInfo() + fd := feed.New(accountInfo, client, -1, 0, logger) topic := utils.HashString("feed-topic1") data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - err = nilFd.DeleteFeed(topic, user1) + err = nilFd.DeleteFeed(topic, user) if !errors.Is(err, feed.ErrReadOnlyFeed) { t.Fatal("read only feed") } - _, err = fd.CreateFeed(user1, topic, data, nil) + err = fd.CreateFeed(user, topic, data, nil) if err != nil { t.Fatal(err) } - err = fd.DeleteFeed(topic, user1) + err = fd.DeleteFeed(topic, user) if err != nil { t.Fatal(err) } }) t.Run("feed-from-topic-delete-errors", func(t *testing.T) { - nilFd := feed.New(&account.Info{}, client, logger) + nilFd := feed.New(&account.Info{}, client, -1, 0, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + user := acc.GetAddress(account.UserAccountIndex) topic := utils.HashString("feed-topic1") - err = nilFd.DeleteFeedFromTopic(topic, user1) + err = nilFd.DeleteFeedFromTopic(topic, user) if !errors.Is(err, feed.ErrReadOnlyFeed) { t.Fatal("read only feed") } diff --git a/pkg/feed/handler.go b/pkg/feed/handler.go index d48b1f39..006ad272 100644 --- a/pkg/feed/handler.go +++ b/pkg/feed/handler.go @@ -21,12 +21,18 @@ import ( "context" "crypto" "encoding/binary" + "encoding/hex" "errors" "fmt" + "github.com/fairdatasociety/fairOS-dfs/pkg/logging" "hash" + "strings" "sync" "sync/atomic" + "time" + bCrypto "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/soc" "github.com/ethersphere/bee/pkg/swarm" bmtlegacy "github.com/ethersphere/bmt/legacy" utilsSigner "github.com/fairdatasociety/fairOS-dfs-utils/signer" @@ -34,9 +40,18 @@ import ( "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore" "github.com/fairdatasociety/fairOS-dfs/pkg/feed/lookup" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/hashicorp/golang-lru/v2/expirable" "golang.org/x/crypto/sha3" ) +type feedItem struct { + User utils.Address + AccountInfo *account.Info + Topic []byte + Data []byte + ShouldCreate bool +} + // Handler is the main object which handles all feed related functionality type Handler struct { accountInfo *account.Info @@ -45,6 +60,9 @@ type Handler struct { HashSize int cache map[uint64]*CacheEntry cacheLock sync.RWMutex + logger logging.Logger + pool *expirable.LRU[string, *feedItem] + evictLock sync.Mutex } // hashPool contains a pool of ready hashers @@ -60,12 +78,13 @@ func init() { } // NewHandler the main handler object that handles all the feed related functions. -func NewHandler(accountInfo *account.Info, client blockstore.Client, hasherPool *bmtlegacy.TreePool) *Handler { +func NewHandler(accountInfo *account.Info, client blockstore.Client, hasherPool *bmtlegacy.TreePool, feedCacheSize int, feedCacheTTL time.Duration, logger logging.Logger) *Handler { fh := &Handler{ accountInfo: accountInfo, client: client, hasherPool: hasherPool, cache: make(map[uint64]*CacheEntry), + logger: logger, } for i := 0; i < hasherCount; i++ { hashfunc := crypto.SHA256.New() @@ -74,9 +93,215 @@ func NewHandler(accountInfo *account.Info, client blockstore.Client, hasherPool } hashPool.Put(hashfunc) } + if feedCacheSize != -1 { + fh.pool = expirable.NewLRU(feedCacheSize, func(key string, value *feedItem) { + fh.evictLock.Lock() + defer fh.evictLock.Unlock() + if value.ShouldCreate { + _, _, err := fh.createSoc(value.User, value.AccountInfo, value.Topic, value.Data) + if err != nil { + logger.Errorf("failed to createSoc onEvict from : %v\n", err) + return + } + } + _, _, err := fh.updateSoc(value.User, value.AccountInfo, value.Topic, value.Data) + if err != nil { + logger.Errorf("failed to updateSoc onEvict: %v\n", err) + return + } + }, feedCacheTTL) + } return fh } +func (h *Handler) commit() { + if h.pool != nil { + h.pool.Purge() + } +} + +func (h *Handler) putInPool(topic []byte, item *feedItem) { + topicHex := hex.EncodeToString(topic) + key := fmt.Sprintf("%s-%s", topicHex, item.User.String()) + it, ok := h.pool.Get(key) + if ok && it.ShouldCreate { + item.ShouldCreate = it.ShouldCreate + } + h.pool.Add(key, item) +} + +func (h *Handler) getSoc(topic []byte, user utils.Address, hint lookup.Epoch) ([]byte, []byte, error) { + topicHex := hex.EncodeToString(topic) + key := fmt.Sprintf("%s-%s", topicHex, user.String()) + if h.pool != nil { + item, ok := h.pool.Get(key) + if ok { + return nil, item.Data, nil + } + } + ctx := context.TODO() + f := new(Feed) + f.User = user + copy(f.Topic[:], topic) + + // create the query from values + q := &Query{Feed: *f} + q.TimeLimit = 0 + q.Hint = hint + if hint == lookup.NoClue { + _, err := h.Lookup(ctx, q) + if err != nil { + return nil, nil, err + } + } else { + _, err := h.LookupEpoch(ctx, q) + if err != nil { + return nil, nil, err + } + } + addr, data, err := h.GetContent(&q.Feed) + if err != nil { // skipcq: TCV-001 + return nil, nil, err + } + return addr.Bytes(), data, nil +} + +func (h *Handler) createSoc(user utils.Address, accountInfo *account.Info, topic, data []byte) (lookup.Epoch, []byte, error) { + var ( + req request + epoch lookup.Epoch + ) + + // fill Feed and Epoc related details + copy(req.ID.Topic[:], topic) + req.ID.User = user + req.Epoch.Level = lookup.HighestLevel + req.Epoch.Time = uint64(time.Now().Unix()) + + // Add initial feed data + req.data = data + + // create the id, hash(topic, epoc) + id, err := h.getId(req.Topic, req.Time, req.Level) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // get the payload id BMT(span, payload) + payloadId, err := h.getPayloadId(data) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // create the signer and the content addressed chunk + signer := bCrypto.NewDefaultSigner(accountInfo.GetPrivateKey()) + ch, err := utils.NewChunkWithSpan(data) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + s := soc.New(id, ch) + sch, err := s.Sign(signer) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // generate the data to sign + toSignBytes, err := toSignDigest(id, ch.Address().Bytes()) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // sign the chunk + signature, err := signer.Sign(toSignBytes) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // set the address and the data for the soc chunk + req.idAddr = sch.Address() + req.binaryData = sch.Data() + // set signature and binary data fields + _, err = h.toChunkContent(&req, id, payloadId) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // send the updated soc chunk to bee + addr, err := h.update(id, user.ToBytes(), signature, ch.Data()) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + return req.Epoch, addr, nil +} + +func (h *Handler) updateSoc(user utils.Address, accountInfo *account.Info, topic, data []byte) (lookup.Epoch, []byte, error) { + var ( + epoch lookup.Epoch + ) + retries := 0 +retry: + ctx := context.Background() + f := new(Feed) + f.User = user + copy(f.Topic[:], topic) + + // get the existing request from DB + req, err := h.newRequest(ctx, f) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + req.Time = uint64(time.Now().Unix()) + req.data = data + // create the id, hash(topic, epoc) + id, err := h.getId(req.Topic, req.Time, req.Level) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // get the payload id BMT(span, payload) + payloadId, err := h.getPayloadId(data) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // create the signer and the content addressed chunk + signer := bCrypto.NewDefaultSigner(accountInfo.GetPrivateKey()) + ch, err := utils.NewChunkWithSpan(data) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + s := soc.New(id, ch) + sch, err := s.Sign(signer) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // generate the data to sign + toSignBytes, err := toSignDigest(id, ch.Address().Bytes()) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // sign the chunk + signature, err := signer.Sign(toSignBytes) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + // set the address and the data for the soc chunk + req.idAddr = sch.Address() + req.binaryData = sch.Data() + // set signature and binary data fields + _, err = h.toChunkContent(req, id, payloadId) + if err != nil { // skipcq: TCV-001 + return epoch, nil, err + } + + address, err := h.update(id, user.ToBytes(), signature, ch.Data()) + if err != nil { + // updating same feed in the same second will lead to "chunk already exists" error. + // This will wait for 1 second and retry the update maxUpdateRetry times. + // It is a very dirty fix for this issue. We should find a better way to handle this. + if strings.Contains(err.Error(), "chunk already exists") && retries < maxUpdateRetry { + retries++ + <-time.After(1 * time.Second) + goto retry + } + return epoch, nil, err + } + + return req.Epoch, address, nil +} + func (h *Handler) update(id, owner, signature, data []byte) ([]byte, error) { // send the SOC chunk addr, err := h.client.UploadSOC(utils.Encode(owner), utils.Encode(id), utils.Encode(signature), data) @@ -157,6 +382,7 @@ func (h *Handler) Lookup(ctx context.Context, query *Query) (*CacheEntry, error) } ch := swarm.NewChunk(addr, data) var request request + if err := h.fromChunk(ch, &request, query, &id); err != nil { return nil, nil } @@ -175,6 +401,41 @@ func (h *Handler) Lookup(ctx context.Context, query *Query) (*CacheEntry, error) return h.updateCache(request) } +// LookupEpoch retrieves a specific query +func (h *Handler) LookupEpoch(ctx context.Context, query *Query) (*CacheEntry, error) { + if query.Hint == lookup.NoClue { + return nil, NewError(errInvalidValue, "hint is required for epoch lookup") + } + + // we can't look for anything without a store + if h.client == nil { // skipcq: TCV-001 + return nil, NewError(errInit, "invalid blockstore") + } + + id := ID{ + Feed: query.Feed, + Epoch: query.Hint, + } + ctx, cancel := context.WithTimeout(ctx, defaultRetrieveTimeout) + defer cancel() + + addr, err := h.getAddress(id.Topic, query.Feed.User, query.Hint) + if err != nil { // skipcq: TCV-001 + return nil, err + } + data, err := h.client.DownloadChunk(ctx, addr.Bytes()) + if err != nil { + return nil, err + } + ch := swarm.NewChunk(addr, data) + var request request + if err := h.fromChunk(ch, &request, query, &id); err != nil { + return nil, err + } + + return h.updateCache(&request) +} + // fromChunk populates this structure from chunk data. It does not verify the signature is valid. func (*Handler) fromChunk(chunk swarm.Chunk, r *request, q *Query, id *ID) error { chunkdata := chunk.Data() @@ -300,9 +561,12 @@ func (h *Handler) newRequest(ctx context.Context, feed *Feed) (request2 *request } request2.Feed = *feed - // if we already have an update, then find next epoch if feedUpdate != nil { + if feedUpdate.Epoch.Level == 0 && feedUpdate.Epoch.Time == now { + <-time.After(time.Second) + now = TimestampProvider.Now().Time + } request2.Epoch = lookup.GetNextEpoch(feedUpdate.Epoch, now) } else { request2.Epoch = lookup.GetFirstEpoch(now) diff --git a/pkg/feed/handler_test.go b/pkg/feed/handler_test.go new file mode 100644 index 00000000..a22c2b98 --- /dev/null +++ b/pkg/feed/handler_test.go @@ -0,0 +1,77 @@ +package feed + +import ( + "io" + "testing" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/ethersphere/bee/pkg/swarm" + bmtlegacy "github.com/ethersphere/bmt/legacy" + "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/logging" +) + +func TestHandler(t *testing.T) { + logger := logging.New(io.Discard, 0) + + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + client := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + + t.Run("new-handler", func(t *testing.T) { + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + + accountInfo := acc.GetUserAccountInfo() + bmtPool := bmtlegacy.NewTreePool(hashFunc, swarm.Branches, bmtlegacy.PoolSize) + handler := NewHandler(accountInfo, client, bmtPool, -1, 0, logger) + //defer handler.Close() + + if handler == nil { + t.Fatal("handler is nil") + } + }) + + t.Run("new-handler", func(t *testing.T) { + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + + accountInfo := acc.GetUserAccountInfo() + bmtPool := bmtlegacy.NewTreePool(hashFunc, swarm.Branches, bmtlegacy.PoolSize) + handler := NewHandler(accountInfo, client, bmtPool, -1, 0, logger) + //defer handler.Close() + + if handler == nil { + t.Fatal("handler is nil") + } + }) + + t.Run("new-handler-nil pool", func(t *testing.T) { + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + + accountInfo := acc.GetUserAccountInfo() + bmtPool := bmtlegacy.NewTreePool(hashFunc, swarm.Branches, bmtlegacy.PoolSize) + handler := NewHandler(accountInfo, client, bmtPool, -1, 0, logger) + //defer handler.Close() + + if handler.pool != nil { + t.Fatal("poll is nol nil") + } + }) +} diff --git a/pkg/feed/tracker/tracker.go b/pkg/feed/tracker/tracker.go new file mode 100644 index 00000000..e17c4d18 --- /dev/null +++ b/pkg/feed/tracker/tracker.go @@ -0,0 +1,441 @@ +package tracker + +// +//import ( +// "bytes" +// "encoding/json" +// "errors" +// "fmt" +// "os" +// "strings" +// "sync" +// +// "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore" +// "github.com/fairdatasociety/fairOS-dfs/pkg/feed" +// "github.com/fairdatasociety/fairOS-dfs/pkg/logging" +// "github.com/fairdatasociety/fairOS-dfs/pkg/utils" +// "github.com/syndtr/goleveldb/leveldb" +// "github.com/syndtr/goleveldb/leveldb/storage" +//) +// +//const ( +// listTopic = "leveldb/storage/files-list2" +//) +// +//var ( +// errFileOpen = errors.New("leveldb/storage: file still open") +//) +// +//func InitFeedsTracker(address utils.Address, username, password string, fd *feed.API, client blockstore.Client, logger logging.Logger) (*leveldb.DB, error) { +// db, err := leveldb.Open(NewMemStorage(fd, client, address, username, password, logger), nil) +// if err != nil { +// return nil, err +// } +// fd.SetUpdateTracker(db) +// logger.Debugf("feed tracker initialised for %s\n", username) +// return db, nil +//} +// +//type memStorageLock struct { +// ms *memStorage +//} +// +//func (lock *memStorageLock) Unlock() { +// ms := lock.ms +// ms.mu.Lock() +// defer ms.mu.Unlock() +// if ms.slock == lock { +// ms.slock = nil +// } +//} +// +//// memStorage is a memory-backed storage. +//type memStorage struct { +// mu sync.Mutex +// slock *memStorageLock +// files map[string]*memFile +// list map[string]storage.FileDesc +// meta storage.FileDesc +// fd *feed.API +// client blockstore.Client +// address utils.Address +// username string +// password string +// logging logging.Logger +//} +// +//// NewMemStorage returns a new memory-backed storage implementation. +//func NewMemStorage(fd *feed.API, client blockstore.Client, address utils.Address, username string, password string, logger logging.Logger) storage.Storage { +// list := make(map[string]storage.FileDesc) +// topic := getTopic([]string{listTopic, username, password}) +// _, dt, err := fd.GetFeedData(topic, address, []byte(password), true) +// if err == nil { +// _ = json.Unmarshal(dt, &list) +// } +// return &memStorage{ +// files: make(map[string]*memFile), +// list: list, +// fd: fd, +// client: client, +// address: address, +// username: username, +// password: password, +// logging: logger, +// } +//} +// +//func (ms *memStorage) Lock() (storage.Locker, error) { +// ms.mu.Lock() +// defer ms.mu.Unlock() +// if ms.slock != nil { +// return nil, storage.ErrLocked +// } +// ms.slock = &memStorageLock{ms: ms} +// return ms.slock, nil +//} +// +//func (ms *memStorage) Log(str string) { +// ms.logging.Debug(str) +//} +// +//func (ms *memStorage) List(ft storage.FileType) ([]storage.FileDesc, error) { +// ms.mu.Lock() +// var fds []storage.FileDesc +// for _, fd := range ms.list { +// if fd.Type&ft != 0 { +// fds = append(fds, fd) +// } +// } +// ms.mu.Unlock() +// return fds, nil +//} +// +//func (ms *memStorage) Open(fd storage.FileDesc) (storage.Reader, error) { +// if !storage.FileDescOk(fd) { +// return nil, storage.ErrInvalidFile +// } +// +// ms.mu.Lock() +// defer ms.mu.Unlock() +// if m, exist := ms.files[fd.String()]; exist { +// if m.open { +// return nil, errFileOpen +// } +// m.open = true +// return &memReader{Reader: bytes.NewReader(m.Bytes()), ms: ms, m: m}, nil +// } +// m := &memFile{} +// m.fd = ms.fd +// m.name = fd.String() +// m.username = ms.username +// m.password = ms.password +// m.address = ms.address +// m.client = ms.client +// topic := getTopic([]string{fd.String(), ms.username, ms.password}) +// _, ref, err := ms.fd.GetFeedData(topic, ms.address, []byte(ms.password), true) +// if err != nil && err.Error() != "feed does not exist or was not updated yet" { +// return nil, os.ErrNotExist +// } +// +// data, _, err := ms.client.DownloadBlob(ref) +// if err != nil { +// return nil, os.ErrNotExist +// } +// m.Buffer = bytes.NewBuffer(data) +// ms.files[fd.String()] = m +// m.open = true +// return &memReader{Reader: bytes.NewReader(m.Bytes()), ms: ms, m: m}, nil +//} +// +//func (ms *memStorage) Create(fd storage.FileDesc) (storage.Writer, error) { +// if !storage.FileDescOk(fd) { +// return nil, storage.ErrInvalidFile +// } +// +// ms.mu.Lock() +// defer ms.mu.Unlock() +// m, exist := ms.files[fd.String()] +// if exist { +// if m.open { +// return nil, errFileOpen +// } +// m.Reset() +// } else { +// m = &memFile{} +// m.fd = ms.fd +// m.name = fd.String() +// m.username = ms.username +// m.password = ms.password +// m.address = ms.address +// m.client = ms.client +// +// m.Buffer = bytes.NewBuffer([]byte{}) +// +// ref, err := ms.client.UploadBlob(m.Buffer.Bytes(), 0, false) +// if err != nil { +// return nil, err +// } +// +// topic := getTopic([]string{fd.String(), ms.username, ms.password}) +// _, err = ms.fd.UpdateFeed(ms.address, topic, ref, []byte(ms.password), true) +// if err != nil { +// return nil, err +// } +// ms.files[fd.String()] = m +// } +// m.open = true +// ms.list[fd.String()] = fd +// dt, err := json.Marshal(ms.list) +// if err == nil { +// topic := getTopic([]string{listTopic, ms.username, ms.password}) +// _, err = ms.fd.UpdateFeed(ms.address, topic, dt, []byte(ms.password), true) +// if err != nil { +// ms.logging.Error("error updating list", "error", err) +// } +// } +// +// return &memWriter{memFile: m, ms: ms, name: fd.String()}, nil +//} +// +//func (ms *memStorage) Remove(fd storage.FileDesc) error { +// if !storage.FileDescOk(fd) { +// return storage.ErrInvalidFile +// } +// +// ms.mu.Lock() +// defer ms.mu.Unlock() +// if _, exist := ms.files[fd.String()]; exist { +// delete(ms.files, fd.String()) +// topic := getTopic([]string{fd.String(), ms.username, ms.password}) +// +// _, err := ms.fd.UpdateFeed(ms.address, topic, []byte(utils.DeletedFeedMagicWord), []byte(ms.password), true) +// if err != nil { +// return err +// } +// +// delete(ms.list, fd.String()) +// dt, err := json.Marshal(ms.list) +// if err != nil { +// return err +// } +// lTopic := getTopic([]string{listTopic, ms.username, ms.password}) +// _, err = ms.fd.UpdateFeed(ms.address, lTopic, dt, []byte(ms.password), true) +// if err != nil { +// return err +// } +// return nil +// } +// return os.ErrNotExist +//} +// +//func (ms *memStorage) Rename(oldfd, newfd storage.FileDesc) error { +// if !storage.FileDescOk(oldfd) || !storage.FileDescOk(newfd) { +// return storage.ErrInvalidFile +// } +// if oldfd == newfd { +// return nil +// } +// +// ms.mu.Lock() +// defer ms.mu.Unlock() +// oldm, exist := ms.files[oldfd.String()] +// if !exist { +// return os.ErrNotExist +// } +// newm, exist := ms.files[newfd.String()] +// if (exist && newm.open) || oldm.open { +// return errFileOpen +// } +// delete(ms.files, oldfd.String()) +// delete(ms.list, oldfd.String()) +// +// ms.files[newfd.String()] = oldm +// ms.list[newfd.String()] = newfd +// +// topic := getTopic([]string{oldfd.String(), ms.username, ms.password}) +// _, ref, err := ms.fd.GetFeedData(topic, ms.address, []byte(ms.password), true) +// if err != nil { +// return err +// } +// +// topic = getTopic([]string{newfd.String(), ms.username, ms.password}) +// _, err = ms.fd.UpdateFeed(ms.address, topic, ref, []byte(ms.password), true) +// if err != nil { +// return err +// } +// +// dt, err := json.Marshal(ms.list) +// if err != nil { +// return err +// } +// +// lTopic := getTopic([]string{listTopic, ms.username, ms.password}) +// _, err = ms.fd.UpdateFeed(ms.address, lTopic, dt, []byte(ms.password), true) +// if err != nil { +// return err +// } +// +// return nil +//} +// +//func (ms *memStorage) Close() error { +// return nil +//} +// +//func (ms *memStorage) setMeta(fd storage.FileDesc) error { +// content := fd.String() +// // Check and backup old CURRENT file. +// currentPath := "CURRENT" +// +// topic := getTopic([]string{currentPath, ms.username, ms.password}) +// _, dt, err := ms.fd.GetFeedData(topic, ms.address, []byte(ms.password), true) +// if err != nil && err.Error() != "feed does not exist or was not updated yet" { +// return err +// } +// if string(dt) == content { +// // Content not changed, do nothing. +// return nil +// } +// +// _, err = ms.fd.UpdateFeed(ms.address, topic, []byte(content), []byte(ms.password), true) +// if err != nil { // skipcq: TCV-001 +// return err +// } +// ms.meta = fd +// return nil +//} +// +//func (ms *memStorage) SetMeta(fd storage.FileDesc) error { +// if !storage.FileDescOk(fd) { +// return storage.ErrInvalidFile +// } +// +// ms.mu.Lock() +// defer ms.mu.Unlock() +// return ms.setMeta(fd) +//} +// +//func (ms *memStorage) GetMeta() (storage.FileDesc, error) { +// ms.mu.Lock() +// defer ms.mu.Unlock() +// +// meta := storage.FileDesc{} +// if ms.meta.Zero() { +// // Try +// // - CURRENT +// currentPath := "CURRENT" +// topic := getTopic([]string{currentPath, ms.username, ms.password}) +// _, dt, err := ms.fd.GetFeedData(topic, ms.address, []byte(ms.password), true) +// if err != nil { +// return meta, os.ErrNotExist +// } +// if !fsParseNamePtr(string(dt), &meta) { +// return meta, os.ErrNotExist +// } +// ms.meta = meta +// return meta, nil +// } +// +// return ms.meta, nil +//} +// +//type memFile struct { +// name string +// *bytes.Buffer +// open bool +// fd *feed.API +// username string +// password string +// address utils.Address +// client blockstore.Client +//} +// +//type memReader struct { +// *bytes.Reader +// ms *memStorage +// m *memFile +// closed bool +//} +// +//func (mr *memReader) Close() error { +// mr.ms.mu.Lock() +// defer mr.ms.mu.Unlock() +// if mr.closed { +// return storage.ErrClosed +// } +// mr.m.open = false +// return nil +//} +// +//type memWriter struct { +// name string +// *memFile +// ms *memStorage +// closed bool +//} +// +//func (mw *memWriter) Write(p []byte) (n int, err error) { +// n, err = mw.memFile.Write(p) +// if err != nil { +// return +// } +// +// ref, err := mw.client.UploadBlob(mw.Bytes(), 0, false) +// if err != nil { +// return +// } +// +// topic := getTopic([]string{mw.name, mw.username, mw.password}) +// _, err = mw.fd.UpdateFeed(mw.address, topic, ref, []byte(mw.password), true) +// return +//} +// +//func (mw *memWriter) Sync() error { +// return nil +//} +// +//func (mw *memWriter) Close() error { +// mw.ms.mu.Lock() +// defer mw.ms.mu.Unlock() +// if mw.closed { +// return storage.ErrClosed +// } +// mw.memFile.open = false +// return nil +//} +// +//func fsParseName(name string) (fd storage.FileDesc, ok bool) { +// var tail string +// _, err := fmt.Sscanf(name, "%d.%s", &fd.Num, &tail) +// if err == nil { +// switch tail { +// case "log": +// fd.Type = storage.TypeJournal +// case "ldb", "sst": +// fd.Type = storage.TypeTable +// case "tmp": +// fd.Type = storage.TypeTemp +// default: +// return +// } +// return fd, true +// } +// n, _ := fmt.Sscanf(name, "MANIFEST-%d%s", &fd.Num, &tail) +// if n == 1 { +// fd.Type = storage.TypeManifest +// return fd, true +// } +// return +//} +// +//func fsParseNamePtr(name string, fd *storage.FileDesc) bool { +// _fd, ok := fsParseName(name) +// if fd != nil { +// *fd = _fd +// } +// return ok +//} +// +//func getTopic(segments []string) []byte { +// return utils.HashString(strings.Join(segments, "/")) +//} diff --git a/pkg/feed/tracker/tracker_test.go b/pkg/feed/tracker/tracker_test.go new file mode 100644 index 00000000..68acfec3 --- /dev/null +++ b/pkg/feed/tracker/tracker_test.go @@ -0,0 +1,198 @@ +package tracker + +// +//import ( +// "fmt" +// "io" +// "testing" +// "time" +// +// "github.com/stretchr/testify/require" +// +// "github.com/fairdatasociety/fairOS-dfs/pkg/account" +// "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" +// "github.com/fairdatasociety/fairOS-dfs/pkg/feed" +// "github.com/fairdatasociety/fairOS-dfs/pkg/feed/lookup" +// "github.com/fairdatasociety/fairOS-dfs/pkg/logging" +// "github.com/fairdatasociety/fairOS-dfs/pkg/utils" +// "github.com/syndtr/goleveldb/leveldb" +//) +// +//func TestTimeKeeper(t *testing.T) { +// logger := logging.New(io.Discard, 0) +// +// acc1 := account.New(logger) +// _, _, err := acc1.CreateUserAccount("") +// if err != nil { +// t.Fatal(err) +// } +// user1 := acc1.GetAddress(account.UserAccountIndex) +// accountInfo1 := acc1.GetUserAccountInfo() +// client := mock.NewMockBeeClient() +// +// t.Run("level-get-from-same-feed-pointer", func(t *testing.T) { +// fd1 := feed.New(accountInfo1, client, logger) +// db, err := leveldb.Open(NewMemStorage(fd1, client, user1, "username", "password", logger), nil) +// if err != nil { +// t.Fatal(err) +// } +// fd1.SetUpdateTracker(db) +// +// topicOne := utils.HashString("topicOne") +// _, err = fd1.GetFeedUpdateEpoch(topicOne) +// if err == nil { +// t.Fatal("feed should not exist") +// } +// +// now := time.Now().Unix() +// err = fd1.PutFeedUpdateEpoch(topicOne, lookup.Epoch{ +// Time: uint64(now), +// Level: 31, +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// epoch, err := fd1.GetFeedUpdateEpoch(topicOne) +// if err != nil { +// t.Fatal(err) +// } +// +// require.Equal(t, uint64(now), epoch.Time) +// +// err = db.Close() +// if err != nil { +// t.Fatal(err) +// } +// }) +// +// t.Run("level-get-from-different-feed-pointer", func(t *testing.T) { +// fd1 := feed.New(accountInfo1, client, logger) +// db, err := leveldb.Open(NewMemStorage(fd1, client, user1, "username", "password", logger), nil) +// if err != nil { +// t.Fatal(err) +// } +// fd1.SetUpdateTracker(db) +// +// topic := utils.HashString("topicTwo") +// _, err = fd1.GetFeedUpdateEpoch(topic) +// if err == nil { +// t.Fatal("feed should not exist") +// } +// +// now := time.Now().Unix() +// err = fd1.PutFeedUpdateEpoch(topic, lookup.Epoch{ +// Time: uint64(now), +// Level: 31, +// }) +// if err != nil { +// t.Fatal(err) +// } +// err = db.Close() +// if err != nil { +// t.Fatal(err) +// } +// +// fd2 := feed.New(accountInfo1, client, logger) +// db2, err := leveldb.Open(NewMemStorage(fd2, client, user1, "username", "password", logger), nil) +// if err != nil { +// t.Fatal(err) +// } +// fd2.SetUpdateTracker(db2) +// epoch, err := fd2.GetFeedUpdateEpoch(topic) +// if err != nil { +// t.Fatal(err) +// } +// +// require.Equal(t, uint64(now), epoch.Time) +// +// err = db2.Close() +// if err != nil { +// t.Fatal(err) +// } +// }) +// +// t.Run("level-get-from-multiple-different-feed-pointer", func(t *testing.T) { +// fd1 := feed.New(accountInfo1, client, logger) +// db, err := leveldb.Open(NewMemStorage(fd1, client, user1, "username", "password", logger), nil) +// if err != nil { +// t.Fatal(err) +// } +// fd1.SetUpdateTracker(db) +// +// now := time.Now().Unix() +// for i := 0; i < 10000; i++ { +// topic := utils.HashString(fmt.Sprintf("topic-%d", i)) +// err = fd1.PutFeedUpdateEpoch(topic, lookup.Epoch{ +// Time: uint64(now + int64(i)), +// Level: 31, +// }) +// if err != nil { +// t.Fatal(err) +// } +// } +// +// err = db.Close() +// if err != nil { +// t.Fatal(err) +// } +// +// fd2 := feed.New(accountInfo1, client, logger) +// db2, err := leveldb.Open(NewMemStorage(fd2, client, user1, "username", "password", logger), nil) +// if err != nil { +// t.Fatal(err) +// } +// fd2.SetUpdateTracker(db2) +// for i := 0; i < 10000; i++ { +// topic := utils.HashString(fmt.Sprintf("topic-%d", i)) +// epoch, err := fd2.GetFeedUpdateEpoch(topic) +// if err != nil { +// t.Fatal(err) +// } +// require.Equal(t, uint64(now+int64(i)), epoch.Time) +// +// } +// err = db2.Close() +// if err != nil { +// t.Fatal(err) +// } +// fd3 := feed.New(accountInfo1, client, logger) +// db3, err := leveldb.Open(NewMemStorage(fd3, client, user1, "username", "password", logger), nil) +// if err != nil { +// t.Fatal(err) +// } +// fd3.SetUpdateTracker(db3) +// for i := 0; i < 10000; i++ { +// topic := utils.HashString(fmt.Sprintf("topic-%d", i)) +// epoch, err := fd3.GetFeedUpdateEpoch(topic) +// if err != nil { +// t.Fatal(err) +// } +// require.Equal(t, uint64(now+int64(i)), epoch.Time) +// +// } +// err = db3.Close() +// if err != nil { +// t.Fatal(err) +// } +// fd4 := feed.New(accountInfo1, client, logger) +// db4, err := leveldb.Open(NewMemStorage(fd4, client, user1, "username", "password", logger), nil) +// if err != nil { +// t.Fatal(err) +// } +// fd4.SetUpdateTracker(db4) +// for i := 0; i < 10000; i++ { +// topic := utils.HashString(fmt.Sprintf("topic-%d", i)) +// epoch, err := fd4.GetFeedUpdateEpoch(topic) +// if err != nil { +// t.Fatal(err) +// } +// require.Equal(t, uint64(now+int64(i)), epoch.Time) +// +// } +// err = db4.Close() +// if err != nil { +// t.Fatal(err) +// } +// }) +//} diff --git a/pkg/file/IFile.go b/pkg/file/IFile.go index ead9e467..c12540c9 100644 --- a/pkg/file/IFile.go +++ b/pkg/file/IFile.go @@ -20,7 +20,7 @@ import "io" // IFile is the interface for file operations type IFile interface { - Upload(fd io.Reader, podFileName string, fileSize int64, blockSize, uint32 uint32, podPath, compression, podPassword string) error + Upload(fd io.Reader, podFileName string, fileSize int64, blockSize, mode uint32, podPath, compression, podPassword string) error Download(podFileWithPath, podPassword string) (io.ReadCloser, uint64, error) ListFiles(files []string, podPassword string) ([]Entry, error) GetStats(podName, podFileWithPath, podPassword string) (*Stats, error) diff --git a/pkg/file/chmod_test.go b/pkg/file/chmod_test.go index cedb14ea..0b9458c8 100644 --- a/pkg/file/chmod_test.go +++ b/pkg/file/chmod_test.go @@ -7,7 +7,10 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/file" @@ -15,13 +18,21 @@ import ( "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestChmod(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -31,7 +42,7 @@ func TestChmod(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { diff --git a/pkg/file/download.go b/pkg/file/download.go index 8bb3daf3..dd644ec8 100644 --- a/pkg/file/download.go +++ b/pkg/file/download.go @@ -20,7 +20,6 @@ import ( "encoding/json" "errors" "io" - "time" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" ) @@ -56,26 +55,29 @@ func (f *File) ReadSeeker(podFileWithPath, podPassword string) (io.ReadSeekClose if meta == nil { // skipcq: TCV-001 return nil, 0, ErrFileNotFound } - fileInodeBytes, _, err := f.getClient().DownloadBlob(meta.InodeAddress) if err != nil { // skipcq: TCV-001 return nil, 0, err } - var fileInode INode err = json.Unmarshal(fileInodeBytes, &fileInode) if err != nil { // skipcq: TCV-001 return nil, 0, err } - // need to change the access time for podFile if it is owned by user - if !f.fd.IsReadOnlyFeed() { - meta.AccessTime = time.Now().Unix() - err = f.updateMeta(meta, podPassword) - if err != nil { // skipcq: TCV-001 - return nil, 0, err + /* + // Disabling access time update for now to reduce latency + // need to change the access time for podFile if it is owned by user + if !f.fd.IsReadOnlyFeed() { + meta.AccessTime = time.Now().Unix() + go func() { + err = f.updateMeta(meta, podPassword) + if err != nil { // skipcq: TCV-001 + f.logger.Errorf("error updating meta for file %s: %s", totalFilePath, err.Error()) + } + }() } - } + */ reader := NewReader(fileInode, f.getClient(), meta.Size, meta.BlockSize, meta.Compression, false) return reader, meta.Size, nil diff --git a/pkg/file/download_test.go b/pkg/file/download_test.go index aae4e1e2..f1bf2359 100644 --- a/pkg/file/download_test.go +++ b/pkg/file/download_test.go @@ -24,7 +24,10 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/file" @@ -32,13 +35,21 @@ import ( "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDownload(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") require.NoError(t, err) @@ -46,7 +57,7 @@ func TestDownload(t *testing.T) { pod1AccountInfo, err := acc.CreatePodAccount(1, false) require.NoError(t, err) - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { @@ -137,7 +148,7 @@ func TestDownload(t *testing.T) { podPassword, _ := utils.GetRandString(pod.PasswordLength) filePath := "/dir1" - fileName := "file1" + fileName := "file2" compression := "" fileSize := int64(100) blockSize := file.MinBlockSize diff --git a/pkg/file/file.go b/pkg/file/file.go index 520ff9b1..254a511b 100644 --- a/pkg/file/file.go +++ b/pkg/file/file.go @@ -128,20 +128,18 @@ func (f *File) GetInode(podPassword, filePath string) *MetaData { // skipcq: TCV return meta } topic := utils.HashString(filePath) - _, metaBytes, err := f.fd.GetFeedData(topic, f.userAddress, []byte(podPassword)) + _, metaBytes, err := f.fd.GetFeedData(topic, f.userAddress, []byte(podPassword), false) if err != nil { return nil } - if string(metaBytes) == utils.DeletedFeedMagicWord { - return nil + return meta } err = json.Unmarshal(metaBytes, &meta) if err != nil { // skipcq: TCV-001 return nil } - f.AddToFileMap(filePath, meta) return meta } @@ -171,7 +169,7 @@ func newLsTask(f *File, topic []byte, path, podPassword string, l *[]Entry, mtx // Execute func (lt *lsTask) Execute(context.Context) error { defer lt.wg.Done() - _, data, err := lt.f.fd.GetFeedData(lt.topic, lt.f.userAddress, []byte(lt.podPassword)) + _, data, err := lt.f.fd.GetFeedData(lt.topic, lt.f.userAddress, []byte(lt.podPassword), false) if err != nil { // skipcq: TCV-001 return fmt.Errorf("file mtdt : %v", err) } diff --git a/pkg/file/ls_test.go b/pkg/file/ls_test.go index a15d6adc..40a3c132 100644 --- a/pkg/file/ls_test.go +++ b/pkg/file/ls_test.go @@ -24,7 +24,10 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/file" @@ -32,11 +35,19 @@ import ( "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestListFiles(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -46,7 +57,7 @@ func TestListFiles(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { diff --git a/pkg/file/meta.go b/pkg/file/meta.go index e4851897..6dd8569e 100644 --- a/pkg/file/meta.go +++ b/pkg/file/meta.go @@ -57,7 +57,7 @@ type MetaData struct { func (f *File) LoadFileMeta(fileNameWithPath, podPassword string) error { meta, err := f.GetMetaFromFileName(fileNameWithPath, podPassword, f.userAddress) if err != nil { // skipcq: TCV-001 - if err == ErrDeletedFeed { + if errors.Is(err, ErrDeletedFeed) { return nil } return err @@ -68,12 +68,12 @@ func (f *File) LoadFileMeta(fileNameWithPath, podPassword string) error { } func (f *File) handleMeta(meta *MetaData, podPassword string) error { - // check if meta is present. - err := f.uploadMeta(meta, podPassword) - if err != nil { + // check if meta is present. if present update else upload + _, err := f.GetMetaFromFileName(utils.CombinePathAndFile(meta.Path, meta.Name), podPassword, f.userAddress) + if err == nil || errors.Is(err, ErrDeletedFeed) { return f.updateMeta(meta, podPassword) } - return nil + return f.uploadMeta(meta, podPassword) } func (f *File) uploadMeta(meta *MetaData, podPassword string) error { @@ -86,20 +86,14 @@ func (f *File) uploadMeta(meta *MetaData, podPassword string) error { // put the file meta as a feed totalPath := utils.CombinePathAndFile(meta.Path, meta.Name) topic := utils.HashString(totalPath) - _, err = f.fd.CreateFeed(f.userAddress, topic, fileMetaBytes, []byte(podPassword)) - return err + return f.fd.CreateFeed(f.userAddress, topic, fileMetaBytes, []byte(podPassword)) } func (f *File) deleteMeta(meta *MetaData, podPassword string) error { totalPath := utils.CombinePathAndFile(meta.Path, meta.Name) topic := utils.HashString(totalPath) // update with utils.DeletedFeedMagicWord - _, err := f.fd.UpdateFeed(f.userAddress, topic, []byte(utils.DeletedFeedMagicWord), []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - - return nil + return f.fd.UpdateFeed(f.userAddress, topic, []byte(utils.DeletedFeedMagicWord), []byte(podPassword), false) } func (f *File) updateMeta(meta *MetaData, podPassword string) error { @@ -112,12 +106,7 @@ func (f *File) updateMeta(meta *MetaData, podPassword string) error { // put the file meta as a feed totalPath := utils.CombinePathAndFile(meta.Path, meta.Name) topic := utils.HashString(totalPath) - _, err = f.fd.UpdateFeed(f.userAddress, topic, fileMetaBytes, []byte(podPassword)) - if err != nil { // skipcq: TCV-001 - return err - } - - return nil + return f.fd.UpdateFeed(f.userAddress, topic, fileMetaBytes, []byte(podPassword), false) } // BackupFromFileName is used to backup a file @@ -185,7 +174,7 @@ func (f *File) RenameFromFileName(fileNameWithPath, newFileNameWithPath, podPass // GetMetaFromFileName is used to get meta from file name func (f *File) GetMetaFromFileName(fileNameWithPath, podPassword string, userAddress utils.Address) (*MetaData, error) { topic := utils.HashString(fileNameWithPath) - _, metaBytes, err := f.fd.GetFeedData(topic, userAddress, []byte(podPassword)) + _, metaBytes, err := f.fd.GetFeedData(topic, userAddress, []byte(podPassword), false) if err != nil { return nil, err } diff --git a/pkg/file/reader.go b/pkg/file/reader.go index 6ca4c900..a0a1214a 100644 --- a/pkg/file/reader.go +++ b/pkg/file/reader.go @@ -25,7 +25,7 @@ import ( "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/golang/snappy" - lru "github.com/hashicorp/golang-lru" + lru "github.com/hashicorp/golang-lru/v2/expirable" "github.com/klauspost/pgzip" ) @@ -50,7 +50,7 @@ type Reader struct { blockCursor uint32 totalSize uint64 compression string - blockCache *lru.Cache + blockCache *lru.LRU[string, []byte] rlBuffer []byte rlOffset int @@ -90,10 +90,6 @@ func (f *File) OpenFileForIndex(podFile, podPassword string) (*Reader, error) { // NewReader create a new reader object to read a file from the pod based on its configuration. func NewReader(fileInode INode, client blockstore.Client, fileSize uint64, blockSize uint32, compression string, cache bool) *Reader { - var blockCache *lru.Cache - if cache { - blockCache, _ = lru.New(blockCacheSize) - } r := &Reader{ fileInode: fileInode, client: client, @@ -101,9 +97,11 @@ func NewReader(fileInode INode, client blockstore.Client, fileSize uint64, block fileSize: fileSize, blockSize: blockSize, compression: compression, - blockCache: blockCache, rlReadNewLine: false, } + if cache { + r.blockCache = lru.NewLRU[string, []byte](blockCacheSize, func(key string, value []byte) {}, 0) + } return r } @@ -195,7 +193,7 @@ func (r *Reader) Read(b []byte) (n int, err error) { } // Seek seeks to a given offset in the file and returns the offset. -func (r *Reader) Seek(seekOffset int64, whence int) (int64, error) { +func (r *Reader) Seek(seekOffset int64, _ int) (int64, error) { // TODO: use whence if seekOffset < 0 || seekOffset > int64(r.fileSize) { return 0, ErrInvalidOffset @@ -312,7 +310,7 @@ func (r *Reader) getBlock(ref []byte, compression string, blockSize uint32) ([]b refStr := utils.NewReference(ref).String() if r.blockCache != nil { if data, found := r.blockCache.Get(refStr); found { - return data.([]byte), nil + return data, nil } } stdoutBytes, _, err := r.client.DownloadBlob(ref) diff --git a/pkg/file/reader_test.go b/pkg/file/reader_test.go index c429d7e6..0140f212 100644 --- a/pkg/file/reader_test.go +++ b/pkg/file/reader_test.go @@ -23,6 +23,12 @@ import ( "math/big" "testing" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/sirupsen/logrus" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/file" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" @@ -31,7 +37,15 @@ import ( ) func TestFileReader(t *testing.T) { - mockClient := mock.NewMockBeeClient() + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) t.Run("read-entire-file-shorter-than-block", func(t *testing.T) { fileSize := uint64(15) @@ -302,7 +316,7 @@ func TestFileReader(t *testing.T) { }) } -func createFile(t *testing.T, fileSize uint64, blockSize uint32, compression string, mockClient *mock.BeeClient) ([]byte, file.INode) { +func createFile(t *testing.T, fileSize uint64, blockSize uint32, compression string, mockClient *bee.Client) ([]byte, file.INode) { var fileBlocks []*file.BlockInfo noOfBlocks := fileSize / uint64(blockSize) if fileSize%uint64(blockSize) != 0 { @@ -347,7 +361,7 @@ func createFile(t *testing.T, fileSize uint64, blockSize uint32, compression str } } -func createFileWithNewlines(t *testing.T, fileSize uint64, blockSize uint32, compression string, mockClient *mock.BeeClient, linesPerBlock uint32) ([]byte, file.INode, int, []byte, int, []byte) { // skipcq: GO-C4008 +func createFileWithNewlines(t *testing.T, fileSize uint64, blockSize uint32, compression string, mockClient *bee.Client, linesPerBlock uint32) ([]byte, file.INode, int, []byte, int, []byte) { // skipcq: GO-C4008 var fileBlocks []*file.BlockInfo noOfBlocks := fileSize / uint64(blockSize) if fileSize%uint64(blockSize) != 0 { diff --git a/pkg/file/rename_test.go b/pkg/file/rename_test.go index d669fd53..edd2d330 100644 --- a/pkg/file/rename_test.go +++ b/pkg/file/rename_test.go @@ -7,21 +7,31 @@ import ( "testing" "time" - "github.com/fairdatasociety/fairOS-dfs/pkg/pod" - + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/file" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestRename(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -31,7 +41,7 @@ func TestRename(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { diff --git a/pkg/file/rm.go b/pkg/file/rm.go index 94231abf..3e83a9ed 100644 --- a/pkg/file/rm.go +++ b/pkg/file/rm.go @@ -52,7 +52,7 @@ func (f *File) RmFile(podFileWithPath, podPassword string) error { err = f.client.DeleteReference(meta.InodeAddress) if err != nil { f.logger.Errorf("could not delete file inode %s", swarm.NewAddress(meta.InodeAddress).String()) - return fmt.Errorf("could not delete file inode %v", swarm.NewAddress(meta.InodeAddress).String()) + return fmt.Errorf("could not delete file inode %s: %s", swarm.NewAddress(meta.InodeAddress).String(), err.Error()) } for _, fblocks := range fInode.Blocks { err = f.client.DeleteReference(fblocks.Reference.Bytes()) @@ -63,11 +63,10 @@ func (f *File) RmFile(podFileWithPath, podPassword string) error { } // remove the meta topic := utils.HashString(totalFilePath) - _, err = f.fd.UpdateFeed(f.userAddress, topic, []byte(utils.DeletedFeedMagicWord), []byte(podPassword)) // empty byte array will fail, so some 1 byte - if err != nil { // skipcq: TCV-001 + err = f.fd.UpdateFeed(f.userAddress, topic, []byte(utils.DeletedFeedMagicWord), []byte(podPassword), false) // empty byte array will fail, so some 1 byte + if err != nil { // skipcq: TCV-001 return err } - // remove the file from file map f.RemoveFromFileMap(totalFilePath) diff --git a/pkg/file/rm_test.go b/pkg/file/rm_test.go index 16fc4f1a..26f28334 100644 --- a/pkg/file/rm_test.go +++ b/pkg/file/rm_test.go @@ -22,24 +22,31 @@ import ( "testing" "time" - "github.com/stretchr/testify/require" - - "github.com/fairdatasociety/fairOS-dfs/pkg/pod" - - "github.com/plexsysio/taskmanager" - - "github.com/fairdatasociety/fairOS-dfs/pkg/file" - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" - + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" ) func TestRemoveFile(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") require.NoError(t, err) @@ -47,7 +54,7 @@ func TestRemoveFile(t *testing.T) { pod1AccountInfo, err := acc.CreatePodAccount(1, false) require.NoError(t, err) - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { diff --git a/pkg/file/stat_test.go b/pkg/file/stat_test.go index 47769014..dee6f886 100644 --- a/pkg/file/stat_test.go +++ b/pkg/file/stat_test.go @@ -24,21 +24,30 @@ import ( "testing" "time" - "github.com/fairdatasociety/fairOS-dfs/pkg/pod" - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" - - "github.com/plexsysio/taskmanager" - + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/file" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestStat(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -48,7 +57,7 @@ func TestStat(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { diff --git a/pkg/file/status_test.go b/pkg/file/status_test.go index af1b3f04..bae2680c 100644 --- a/pkg/file/status_test.go +++ b/pkg/file/status_test.go @@ -2,12 +2,16 @@ package file_test import ( "context" - "fmt" "io" "testing" "time" + "github.com/stretchr/testify/require" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/file" @@ -15,11 +19,19 @@ import ( "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestStatus(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -29,7 +41,7 @@ func TestStatus(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { @@ -38,6 +50,7 @@ func TestStatus(t *testing.T) { podPassword, _ := utils.GetRandString(pod.PasswordLength) t.Run("sync-status-file", func(t *testing.T) { + t.Skip() fileObject := file.NewFile("pod1", mockClient, fd, user, tm, logger) numberOfChunks := int64(10) // upload a file @@ -56,10 +69,7 @@ func TestStatus(t *testing.T) { if err != nil { t.Fatal(err) } - fmt.Println("total", total, "numberOfChunks", numberOfChunks) - if total != numberOfChunks { - t.Fatal("chunk count mismatch for status") - } + require.Equal(t, total, numberOfChunks) }) } diff --git a/pkg/file/upload.go b/pkg/file/upload.go index a5aa7b32..5c07ebdc 100644 --- a/pkg/file/upload.go +++ b/pkg/file/upload.go @@ -79,7 +79,6 @@ func (f *File) Upload(fd io.Reader, podFileName string, fileSize int64, blockSiz ModificationTime: now, Mode: mode, } - var totalLength uint64 i := 0 errC := make(chan error) diff --git a/pkg/file/upload_test.go b/pkg/file/upload_test.go index 298d0c03..ed3cdea6 100644 --- a/pkg/file/upload_test.go +++ b/pkg/file/upload_test.go @@ -27,20 +27,30 @@ import ( "testing" "time" - "github.com/fairdatasociety/fairOS-dfs/pkg/pod" - + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/file" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestUpload(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -50,7 +60,7 @@ func TestUpload(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { @@ -216,6 +226,7 @@ func TestUpload(t *testing.T) { if err != nil { t.Fatal(err) } + meta2 := fileObject.GetInode(podPassword, utils.CombinePathAndFile(filepath.ToSlash(filePath), filepath.ToSlash(string(os.PathSeparator)+fileName))) if meta2 != nil { t.Fatal("meta2 should be nil") @@ -318,7 +329,6 @@ func TestUpload(t *testing.T) { if err != nil { t.Fatal(err) } - meta2 := fileObject.GetInode(podPassword, fp) if meta2 != nil { t.Fatal("meta2 should be nil") diff --git a/pkg/file/writeAt_test.go b/pkg/file/writeAt_test.go index 05e74e5e..64a0baa8 100644 --- a/pkg/file/writeAt_test.go +++ b/pkg/file/writeAt_test.go @@ -11,7 +11,10 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/file" @@ -19,6 +22,8 @@ import ( "github.com/fairdatasociety/fairOS-dfs/pkg/pod" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" "go.uber.org/goleak" ) @@ -27,8 +32,15 @@ func TestMain(m *testing.M) { } func TestWriteAt(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -38,7 +50,7 @@ func TestWriteAt(t *testing.T) { if err != nil { t.Fatal(err) } - fd := feed.New(pod1AccountInfo, mockClient, logger) + fd := feed.New(pod1AccountInfo, mockClient, -1, 0, logger) user := acc.GetAddress(1) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { @@ -49,7 +61,7 @@ func TestWriteAt(t *testing.T) { t.Run("writeAt-non-existent-file", func(t *testing.T) { filePath := string(os.PathSeparator) - fileName := "file1" + fileName, _ := utils.GetRandString(10) var offset uint64 = 3 @@ -68,7 +80,7 @@ func TestWriteAt(t *testing.T) { t.Run("upload-update-known-very-small-file", func(t *testing.T) { filePath := string(os.PathSeparator) - fileName := "file1" + fileName, _ := utils.GetRandString(10) compression := "" blockSize := file.MinBlockSize var offset uint64 = 3 @@ -155,21 +167,18 @@ func TestWriteAt(t *testing.T) { if !bytes.Equal(updatedContent, rcvdBuffer.Bytes()) { t.Fatal("content is different") } - - err = fileObject.RmFile(utils.CombinePathAndFile(filepath.ToSlash(filePath), filepath.ToSlash(string(os.PathSeparator)+fileName)), podPassword) + err = fileObject.RmFile(fp, podPassword) if err != nil { t.Fatal(err) } - meta2 := fileObject.GetInode(podPassword, fp) - if meta2 != nil { - t.Fatal("meta2 should be nil") - } + assert.Equal(t, meta2, (*file.MetaData)(nil)) + }) t.Run("upload-update-known-very-small-file-two", func(t *testing.T) { filePath := string(os.PathSeparator) - fileName := "file1" + fileName, _ := utils.GetRandString(10) compression := "" blockSize := file.MinBlockSize var offset uint64 = 4 @@ -281,21 +290,19 @@ func TestWriteAt(t *testing.T) { if !bytes.Equal(updatedContent2, rcvdBuffer.Bytes()) { t.Fatal("content is different") } - - err = fileObject.RmFile(utils.CombinePathAndFile(filepath.ToSlash(filePath), filepath.ToSlash(string(os.PathSeparator)+fileName)), podPassword) + err = fileObject.RmFile(fp, podPassword) if err != nil { t.Fatal(err) } meta2 := fileObject.GetInode(podPassword, fp) - if meta2 != nil { - t.Fatal("meta2 should be nil") - } + assert.Equal(t, meta2, (*file.MetaData)(nil)) + }) t.Run("upload-update-truncate-known-very-small-file", func(t *testing.T) { filePath := string(os.PathSeparator) - fileName := "file1" + fileName, _ := utils.GetRandString(10) compression := "" blockSize := file.MinBlockSize var offset uint64 = 0 @@ -358,33 +365,30 @@ func TestWriteAt(t *testing.T) { if !bytes.Equal(updatedContent, rcvdBuffer.Bytes()) { t.Fatal("content is different") } - - err = fileObject.RmFile(utils.CombinePathAndFile(filepath.ToSlash(filePath), filepath.ToSlash(string(os.PathSeparator)+fileName)), podPassword) + err = fileObject.RmFile(fp, podPassword) if err != nil { t.Fatal(err) } - meta2 := fileObject.GetInode(podPassword, fp) - if meta2 != nil { - t.Fatal("meta2 should be nil") - } + assert.Equal(t, meta2, (*file.MetaData)(nil)) }) t.Run("upload-update-small-file", func(t *testing.T) { - filePath := "/dir1" - fileName := "file1" + filePath := string(os.PathSeparator) + fileName, _ := utils.GetRandString(10) compression := "" - fileSize := int64(100) + fileSize := int64(1000) blockSize := file.MinBlockSize fileObject := file.NewFile("pod1", mockClient, fd, user, tm, logger) - dt, err := uploadFile(t, fileObject, filePath, fileName, compression, podPassword, fileSize, blockSize) + pp := "" + dt, err := uploadFile(t, fileObject, filePath, fileName, compression, pp, fileSize, blockSize) if err != nil { t.Fatal(err) } // check for meta fp := utils.CombinePathAndFile(filepath.ToSlash(filePath), fileName) - meta := fileObject.GetInode(podPassword, fp) + meta := fileObject.GetInode(pp, fp) if meta == nil { t.Fatalf("file not added in file map") } @@ -403,28 +407,30 @@ func TestWriteAt(t *testing.T) { t.Fatalf("invalid block size in meta") } - err = fileObject.LoadFileMeta(filePath+"/"+fileName, podPassword) + err = fileObject.LoadFileMeta(filepath.ToSlash(filePath+fileName), pp) if err != nil { t.Fatal(err) } // skipcq: GSC-G404 rnd := rand.New(rand.NewSource(time.Now().UnixNano())) - min := 0 + min := int(fileSize / 2) max := int(fileSize) offset := rnd.Intn((max - min + 1) + min) + content, err := utils.GetRandBytes(offset) if err != nil { t.Fatal(err) } r := bytes.NewReader(content) - n, err := fileObject.WriteAt(fp, podPassword, r, uint64(offset), false) + n, err := fileObject.WriteAt(fp, pp, r, uint64(offset), false) if n != offset { t.Fatalf("Failed to update %d bytes", offset-n) } if err != nil { t.Fatal(err) } - reader, _, err := fileObject.Download(fp, podPassword) + + reader, _, err := fileObject.Download(fp, pp) if err != nil { t.Fatal(err) } @@ -442,20 +448,19 @@ func TestWriteAt(t *testing.T) { if !bytes.Equal(updatedContent, rcvdBuffer.Bytes()) { t.Fatal("content is different") } - err = fileObject.RmFile(utils.CombinePathAndFile(filepath.ToSlash(filePath), filepath.ToSlash(string(os.PathSeparator)+fileName)), podPassword) + + err = fileObject.RmFile(fp, pp) if err != nil { t.Fatal(err) } - meta2 := fileObject.GetInode(podPassword, fp) - if meta2 != nil { - t.Fatal("meta2 should be nil") - } + meta2 := fileObject.GetInode(pp, fp) + assert.Equal(t, meta2, (*file.MetaData)(nil)) }) t.Run("upload-update-small-file-at-root-with-prefix-snappy", func(t *testing.T) { filePath := string(os.PathSeparator) - fileName := "file2" + fileName, _ := utils.GetRandString(10) compression := "snappy" fileSize := int64(100) blockSize := file.MinBlockSize @@ -521,21 +526,19 @@ func TestWriteAt(t *testing.T) { if !bytes.Equal(updatedContent, rcvdBuffer.Bytes()) { t.Fatal("content is different") } - - err = fileObject.RmFile(utils.CombinePathAndFile(filepath.ToSlash(filePath), filepath.ToSlash(string(os.PathSeparator)+fileName)), podPassword) + err = fileObject.RmFile(fp, podPassword) if err != nil { t.Fatal(err) } meta2 := fileObject.GetInode(podPassword, fp) - if meta2 != nil { - t.Fatal("meta2 should be nil") - } + assert.Equal(t, meta2, (*file.MetaData)(nil)) + }) t.Run("upload-update-small-file-at-root-with-prefix-gzip", func(t *testing.T) { filePath := "/dir1" - fileName := "file10" + fileName, _ := utils.GetRandString(10) compression := "gzip" fileSize := int64(100) blockSize := file.MinBlockSize @@ -603,16 +606,14 @@ func TestWriteAt(t *testing.T) { t.Log("downloadedContent", rcvdBuffer.Bytes()) t.Fatal("content is different ") } - - err = fileObject.RmFile(utils.CombinePathAndFile(filepath.ToSlash(filePath), filepath.ToSlash(string(os.PathSeparator)+fileName)), podPassword) + err = fileObject.RmFile(fp, podPassword) if err != nil { t.Fatal(err) } meta2 := fileObject.GetInode(podPassword, fp) - if meta2 != nil { - t.Fatal("meta2 should be nil") - } + assert.Equal(t, meta2, (*file.MetaData)(nil)) + }) } diff --git a/pkg/pod/close.go b/pkg/pod/close.go index e341cf25..ac26b66e 100644 --- a/pkg/pod/close.go +++ b/pkg/pod/close.go @@ -16,6 +16,8 @@ limitations under the License. package pod +import "fmt" + // ClosePod closed an already opened pod and removes its information from directory and file // data structures. func (p *Pod) ClosePod(podName string) error { @@ -23,6 +25,12 @@ func (p *Pod) ClosePod(podName string) error { if err != nil { // skipcq: TCV-001 return err } + if err := podInfo.feed.Close(); err != nil { + return err + } + if err := p.fd.Close(); err != nil { + return err + } podIndex, err := p.getPodIndex(podName) if err != nil { // skipcq: TCV-001 @@ -36,3 +44,14 @@ func (p *Pod) ClosePod(podName string) error { p.acc.DeletePodAccount(podIndex) return nil } + +// CloseAllPods closes all opened pods and removes their information from directory and file +// data structures. +func (p *Pod) CloseAllPods() error { + for podName := range p.podMap { + if err := p.ClosePod(podName); err != nil { + return fmt.Errorf("error closing pod %s: %w", podName, err) + } + } + return nil +} diff --git a/pkg/pod/commitFeeds.go b/pkg/pod/commitFeeds.go new file mode 100644 index 00000000..6b1b4182 --- /dev/null +++ b/pkg/pod/commitFeeds.go @@ -0,0 +1,12 @@ +package pod + +// CommitFeeds commits uncommitted feeds saved in local lru cache to swarm +func (p *Pod) CommitFeeds(podName string) error { + podInfo, _, err := p.GetPodInfoFromPodMap(podName) + if err != nil { // skipcq: TCV-001 + return err + } + podInfo.feed.CommitFeeds() + p.fd.CommitFeeds() + return nil +} diff --git a/pkg/pod/del.go b/pkg/pod/del.go index 7cbe06fe..1b45c75c 100644 --- a/pkg/pod/del.go +++ b/pkg/pod/del.go @@ -22,7 +22,7 @@ import ( // DeleteOwnPod removed a pod and the list of pods belonging to a user. func (p *Pod) DeleteOwnPod(podName string) error { - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { // skipcq: TCV-001 return err } @@ -60,16 +60,15 @@ func (p *Pod) DeleteOwnPod(podName string) error { p.acc.DeletePodAccount(podIndex) // remove the pod finally - return p.storeUserPods(podList) + return p.storeUserPodsV2(podList) } // DeleteSharedPod removed a pod and the list of pods shared by other users. func (p *Pod) DeleteSharedPod(podName string) error { - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { // skipcq: TCV-001 return err } - found := false for index, pod := range podList.SharedPods { if pod.Name == podName { @@ -85,5 +84,5 @@ func (p *Pod) DeleteSharedPod(podName string) error { p.removePodFromPodMap(podName) // remove the pod finally - return p.storeUserPods(podList) + return p.storeUserPodsV2(podList) } diff --git a/pkg/pod/fork.go b/pkg/pod/fork.go index abc51f39..9348f12d 100644 --- a/pkg/pod/fork.go +++ b/pkg/pod/fork.go @@ -35,8 +35,8 @@ func (p *Pod) PodFork(podName, forkName string) error { } directory := podInfo.GetDirectory() - rootInode := directory.GetInode(podInfo.GetPodPassword(), "/") - if rootInode == nil { + rootInode, err := directory.GetInode(podInfo.GetPodPassword(), "/") + if err != nil { return fmt.Errorf("root inode not found") } return cloneFolder(podInfo, forkInfo, "/", rootInode) @@ -64,7 +64,7 @@ func (p *Pod) PodForkFromRef(forkName, refString string) error { address := utils.HexToAddress(shareInfo.Address) accountInfo.SetAddress(address) - fd := feed.New(accountInfo, p.client, p.logger) + fd := feed.New(accountInfo, p.client, p.feedCacheSize, p.feedCacheTTL, p.logger) file := f.NewFile(shareInfo.PodName, p.client, fd, accountInfo.GetAddress(), p.tm, p.logger) dir := d.NewDirectory(shareInfo.PodName, p.client, fd, accountInfo.GetAddress(), file, p.tm, p.logger) podInfo := &Info{ @@ -125,8 +125,11 @@ func cloneFolder(source, dst *Info, dirNameWithPath string, dirInode *d.Inode) e } else if strings.HasPrefix(fileOrDirName, "_D_") { dirName := strings.TrimPrefix(fileOrDirName, "_D_") path := utils.CombinePathAndFile(dirNameWithPath, dirName) - iNode := source.GetDirectory().GetInode(source.GetPodPassword(), path) - err := dst.GetDirectory().MkDir(path, dst.GetPodPassword(), 0) + iNode, err := source.GetDirectory().GetInode(source.GetPodPassword(), path) + if err != nil { // skipcq: TCV-001 + return err + } + err = dst.GetDirectory().MkDir(path, dst.GetPodPassword(), 0) if err != nil { // skipcq: TCV-001 return err } diff --git a/pkg/pod/group.go b/pkg/pod/group.go new file mode 100644 index 00000000..cc51780d --- /dev/null +++ b/pkg/pod/group.go @@ -0,0 +1,460 @@ +package pod + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/acl" + aclController "github.com/fairdatasociety/fairOS-dfs/pkg/acl/acl" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore" + c "github.com/fairdatasociety/fairOS-dfs/pkg/collection" + d "github.com/fairdatasociety/fairOS-dfs/pkg/dir" + "github.com/fairdatasociety/fairOS-dfs/pkg/feed" + f "github.com/fairdatasociety/fairOS-dfs/pkg/file" + "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + hdwallet "github.com/miguelmota/go-ethereum-hdwallet" + "github.com/tyler-smith/go-bip39" +) + +const ( + GroupFile = "Groups" +) + +var ( + // ErrGroupAlreadyExists is returned when a group already exists + ErrGroupAlreadyExists = fmt.Errorf("group already exists") + + // ErrGroupDoesNotExist is returned when a group does not exist + ErrGroupDoesNotExist = fmt.Errorf("group does not exist") + + ErrPermissionDenied = fmt.Errorf("permission denied") +) + +// Group is the main struct which acts on groups +type Group struct { + fd *feed.API + acc *account.Account + client blockstore.Client + logger logging.Logger + acl acl.ACL + groupsMap map[string]*Info // podName -> dir + groupMu *sync.RWMutex +} + +// GroupItem defines the structure for a group +type GroupItem struct { + Name string `json:"name"` + OwnerPublicKey []byte `json:"ownerPublicKey"` + OwnerAddress string `json:"ownerAddress"` + Password string `json:"password"` + Secret []byte `json:"secret"` +} + +// GroupList lists all the groups +type GroupList struct { + Groups []GroupItem `json:"groups"` + SharedGroups []GroupItem `json:"sharedGroups"` +} + +// NewGroup creates the main group object which has all the methods related to the groups. +func NewGroup(client blockstore.Client, feed *feed.API, account *account.Account, acl acl.ACL, logger logging.Logger) *Group { + return &Group{ + fd: feed, + acc: account, + client: client, + logger: logger, + acl: acl, + groupsMap: make(map[string]*Info), + groupMu: &sync.RWMutex{}, + } +} + +func (g *Group) addPodToPodMap(name string, info *Info) { + g.groupMu.Lock() + defer g.groupMu.Unlock() + g.groupsMap[name] = info +} + +func (g *Group) removePodFromPodMap(name string) { + g.groupMu.Lock() + defer g.groupMu.Unlock() + delete(g.groupsMap, name) +} + +// GetGroupInfoFromMap returns the group info for the given group name. +func (g *Group) GetGroupInfoFromMap(name string) (*Info, string, error) { + g.groupMu.Lock() + defer g.groupMu.Unlock() + if podInfo, ok := g.groupsMap[name]; ok { + return podInfo, podInfo.podPassword, nil + } + return nil, "", fmt.Errorf("could not find pod: %s", name) +} + +// CreateGroup creates a new Group +func (g *Group) CreateGroup(name string) (*Info, error) { + // sanitise: check name for spaces + name = strings.TrimSpace(name) + + groups, err := g.ListGroup() + if err != nil && !errors.Is(err, f.ErrFileNotFound) { // skipcq: TCV-001 + return nil, err + } + if g.checkIfPodPresent(groups, name) { + return nil, ErrGroupAlreadyExists + } + + // generate a new mnemonic: GroupSecret + entropy, err := bip39.NewEntropy(128) + if err != nil { // skipcq: TCV-001 + return nil, err + } + mnemonic, err := bip39.NewMnemonic(entropy) + if err != nil { // skipcq: TCV-001 + return nil, err + } + seed, err := hdwallet.NewSeedFromMnemonic(mnemonic) + if err != nil { // skipcq: TCV-001 + return nil, err + } + + // Encrypt the GroupSecret with the user's private key + key, err := utils.EncryptBytes(crypto.FromECDSA(g.acc.GetUserAccountInfo().GetPrivateKey()), seed) + if err != nil { // skipcq: TCV-001 + return nil, err + } + + address := g.acc.GetUserAccountInfo().GetAddress() + commonAddr := common.HexToAddress(address.Hex()) + addressStr := commonAddr.Hex() + podPasswordBytes, _ := utils.GetRandBytes(PasswordLength) + podPassword := hex.EncodeToString(podPasswordBytes) + group := &GroupItem{ + Name: name, + Secret: key, + OwnerPublicKey: crypto.FromECDSAPub(g.acc.GetUserAccountInfo().GetPublicKey()), + OwnerAddress: commonAddr.Hex(), + Password: podPassword, + } + + // Save in te groups list + groups.Groups = append(groups.Groups, *group) + + // Save the groups list + err = g.store(groups) + if err != nil { // skipcq: TCV-001 + return nil, err + } + + err = g.acl.CreateGroup(name, addressStr) + if err != nil { + return nil, err + } + acc := account.New(g.logger) + err = acc.LoadUserAccountFromSeed(seed) + if err != nil { // skipcq: TCV-001 + return nil, err + } + accountInfo := acc.GetUserAccountInfo() + // load encrypted private key + fd := feed.New(accountInfo, g.client, -1, 0, g.logger) + file := f.NewFile(name, g.client, fd, accountInfo.GetAddress(), nil, g.logger) + dir := d.NewDirectory(name, g.client, fd, accountInfo.GetAddress(), file, nil, g.logger) + kvStore := c.NewKeyValueStore(name, fd, accountInfo, accountInfo.GetAddress(), g.client, g.logger) + docStore := c.NewDocumentStore(name, fd, accountInfo, accountInfo.GetAddress(), file, nil, g.client, g.logger) + + podInfo := &Info{ + podName: name, + podPassword: group.Password, + userAddress: accountInfo.GetAddress(), + accountInfo: accountInfo, + feed: fd, + dir: dir, + file: file, + kvStore: kvStore, + docStore: docStore, + } + + g.addPodToPodMap(podInfo.GetPodName(), podInfo) + return podInfo, podInfo.GetDirectory().MkRootDir(podInfo.GetPodName(), podPassword, podInfo.GetPodAddress(), podInfo.GetFeed()) +} + +// RemoveGroup removes a group +func (g *Group) RemoveGroup(groupName string) error { + // check if group exists + groupName = strings.TrimSpace(groupName) + + groups, err := g.ListGroup() + if err != nil && !errors.Is(err, f.ErrFileNotFound) { // skipcq: TCV-001 + return err + } + found := false + for index, pod := range groups.Groups { + if pod.Name == groupName { + groups.Groups = append(groups.Groups[:index], groups.Groups[index+1:]...) + found = true + } + } + if !found { + return ErrGroupDoesNotExist + } + + gi, err := g.OpenGroup(groupName) + if err != nil { + return err + } + err = gi.GetDocStore().DeleteAllDocumentDBs(gi.GetPodPassword()) + if err != nil { + return err + } + + err = gi.GetKVStore().DeleteAllKVTables(gi.GetPodPassword()) + if err != nil { + return err + } + + address := g.acc.GetUserAccountInfo().GetAddress() + addressStr := address.Hex() + err = g.acl.RemoveGroup(groupName, addressStr) + if err != nil { + return err + } + g.removePodFromPodMap(groupName) + + return g.store(groups) +} + +// RemoveSharedGroup removes a group from sharedGroup list +func (g *Group) RemoveSharedGroup(groupName string) error { + // check if group exists + groupName = strings.TrimSpace(groupName) + + groups, err := g.ListGroup() + if err != nil && !errors.Is(err, f.ErrFileNotFound) { // skipcq: TCV-001 + return err + } + found := false + for index, group := range groups.SharedGroups { + if group.Name == groupName { + groups.SharedGroups = append(groups.SharedGroups[:index], groups.SharedGroups[index+1:]...) + found = true + } + } + if !found { + return ErrGroupDoesNotExist + } + + g.removePodFromPodMap(groupName) + + return g.store(groups) +} + +func (*Group) checkIfPodPresent(groups *GroupList, name string) bool { + if groups == nil || groups.Groups == nil { + return false + } + for _, group := range groups.Groups { + if group.Name == name { + return true + } + } + for _, group := range groups.SharedGroups { + if group.Name == name { + return true + } + } + return false +} + +func (g *Group) ListGroup() (*GroupList, error) { + // load groups from GroupsFile + return g.load() +} + +// OpenGroup opens a new Group +func (g *Group) OpenGroup(name string) (*Info, error) { + // sanitise: check name for spaces + name = strings.TrimSpace(name) + + pi, _, _ := g.GetGroupInfoFromMap(name) + if pi != nil { + return pi, nil + } + + groups, err := g.ListGroup() + if err != nil { // skipcq: TCV-001 + return nil, err + } + + var gr *GroupItem + shared := false + for _, group := range groups.Groups { + if group.Name == name { + gr = &group + break + } + } + if gr == nil { + for _, group := range groups.SharedGroups { + if group.Name == name { + gr = &group + shared = true + break + } + } + } + + if gr == nil { + return nil, ErrGroupDoesNotExist + } + + var ( + accountInfo *account.Info + file *f.File + fd *feed.API + dir *d.Directory + ) + if shared { + permission, err := g.GetPermission(gr.Name) + if err != nil { // skipcq: TCV-001 + return nil, err + } + if permission != aclController.PermissionRead && permission != aclController.PermissionWrite { + _ = g.RemoveSharedGroup(gr.Name) + return nil, ErrPermissionDenied + } + ownerPublicKey, err := crypto.UnmarshalPubkey(gr.OwnerPublicKey) + if err != nil { // skipcq: TCV-001 + return nil, err + } + a, _ := ownerPublicKey.Curve.ScalarMult(ownerPublicKey.X, ownerPublicKey.Y, g.acc.GetUserAccountInfo().GetPrivateKey().D.Bytes()) + secret := sha256.Sum256(a.Bytes()) + seed, err := utils.DecryptBytes(secret[:], gr.Secret) + if err != nil { // skipcq: TCV-001 + return nil, err + } + acc := account.New(g.logger) + err = acc.LoadUserAccountFromSeed(seed) + if err != nil { // skipcq: TCV-001 + return nil, err + } + accountInfo = acc.GetUserAccountInfo() + + if permission == aclController.PermissionRead { + readAccount := g.acc.GetEmptyAccountInfo() + readAccount.SetAddress(accountInfo.GetAddress()) + + fd = feed.New(readAccount, g.client, -1, 0, g.logger) + file = f.NewFile(name, g.client, fd, readAccount.GetAddress(), nil, g.logger) + dir = d.NewDirectory(name, g.client, fd, readAccount.GetAddress(), file, nil, g.logger) + } else { + fd = feed.New(accountInfo, g.client, -1, 0, g.logger) + file = f.NewFile(name, g.client, fd, accountInfo.GetAddress(), nil, g.logger) + dir = d.NewDirectory(name, g.client, fd, accountInfo.GetAddress(), file, nil, g.logger) + } + } else { + seed, err := utils.DecryptBytes(crypto.FromECDSA(g.acc.GetUserAccountInfo().GetPrivateKey()), gr.Secret) + if err != nil { // skipcq: TCV-001 + return nil, err + } + + acc := account.New(g.logger) + err = acc.LoadUserAccountFromSeed(seed) + if err != nil { // skipcq: TCV-001 + return nil, err + } + accountInfo = acc.GetUserAccountInfo() + + // load encrypted private key + fd = feed.New(accountInfo, g.client, -1, 0, g.logger) + file = f.NewFile(name, g.client, fd, accountInfo.GetAddress(), nil, g.logger) + dir = d.NewDirectory(name, g.client, fd, accountInfo.GetAddress(), file, nil, g.logger) + } + kvStore := c.NewKeyValueStore(name, fd, accountInfo, accountInfo.GetAddress(), g.client, g.logger) + docStore := c.NewDocumentStore(name, fd, accountInfo, accountInfo.GetAddress(), file, nil, g.client, g.logger) + podInfo := &Info{ + podName: name, + podPassword: gr.Password, + userAddress: accountInfo.GetAddress(), + accountInfo: accountInfo, + feed: fd, + dir: dir, + file: file, + kvStore: kvStore, + docStore: docStore, + } + g.addPodToPodMap(podInfo.GetPodName(), podInfo) + return podInfo, nil +} + +// CloseGroup closed an already opened group and removes its information from directory and file +// data structures. +func (g *Group) CloseGroup(podName string) error { + podInfo, _, err := g.GetGroupInfoFromMap(podName) + if err != nil { // skipcq: TCV-001 + return err + } + if err := podInfo.feed.Close(); err != nil { + return err + } + if err := g.fd.Close(); err != nil { + return err + } + + // remove from all thr maps + podInfo.dir.RemoveAllFromDirectoryMap() + podInfo.file.RemoveAllFromFileMap() + g.removePodFromPodMap(podName) + return nil +} + +func (g *Group) load() (*GroupList, error) { + list := &GroupList{ + Groups: []GroupItem{}, + } + f2 := f.NewFile("", g.client, g.fd, g.acc.GetAddress(account.UserAccountIndex), nil, g.logger) + topicString := utils.CombinePathAndFile("", GroupFile) + privKeyBytes := crypto.FromECDSA(g.acc.GetUserAccountInfo().GetPrivateKey()) + r, _, err := f2.Download(topicString, hex.EncodeToString(privKeyBytes)) + if err != nil { // skipcq: TCV-001 + return list, err + } + + data, err := io.ReadAll(r) + if err != nil { // skipcq: TCV-001 + return list, err + } + + if len(data) == 0 { + return list, nil + } + + err = json.Unmarshal(data, list) + if err != nil { // skipcq: TCV-001 + return list, err + } + + return list, nil +} + +func (g *Group) store(list *GroupList) error { + data, err := json.Marshal(list) + if err != nil { + return err + } + + f2 := f.NewFile("", g.client, g.fd, g.acc.GetAddress(account.UserAccountIndex), nil, g.logger) + privKeyBytes := crypto.FromECDSA(g.acc.GetUserAccountInfo().GetPrivateKey()) + return f2.Upload(bytes.NewReader(data), GroupFile, int64(len(data)), f.MinBlockSize, 0, "/", "gzip", hex.EncodeToString(privKeyBytes)) +} diff --git a/pkg/pod/groupMember.go b/pkg/pod/groupMember.go new file mode 100644 index 00000000..b3924ca2 --- /dev/null +++ b/pkg/pod/groupMember.go @@ -0,0 +1,213 @@ +package pod + +import ( + "crypto/ecdsa" + "crypto/sha256" + "encoding/json" + "errors" + "strings" + + aclController "github.com/fairdatasociety/fairOS-dfs/pkg/acl/acl" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + + "github.com/ethereum/go-ethereum/common" + f "github.com/fairdatasociety/fairOS-dfs/pkg/file" +) + +func (g *Group) AddMember(groupName string, memberAddress common.Address, memberPublicKey *ecdsa.PublicKey, permission uint8) ([]byte, error) { + // check if group exists + groupName = strings.TrimSpace(groupName) + + groups, err := g.ListGroup() + if err != nil && !errors.Is(err, f.ErrFileNotFound) { // skipcq: TCV-001 + return nil, err + } + if !g.checkIfPodPresent(groups, groupName) { + return nil, ErrGroupDoesNotExist + } + + // encrypt mnemonic DH key secret with member's public key + a, _ := memberPublicKey.Curve.ScalarMult(memberPublicKey.X, memberPublicKey.Y, g.acc.GetUserAccountInfo().GetPrivateKey().D.Bytes()) + secret := sha256.Sum256(a.Bytes()) + + var gr *GroupItem + for _, group := range groups.Groups { + if group.Name == groupName { + gr = &group + break + } + } + if gr == nil { + return nil, ErrGroupDoesNotExist + } + + seed, err := utils.DecryptBytes(crypto.FromECDSA(g.acc.GetUserAccountInfo().GetPrivateKey()), gr.Secret) + if err != nil { // skipcq: TCV-001 + return nil, err + } + + encData, err := utils.EncryptBytes(secret[:], seed) + if err != nil { + return nil, err + } + + address := g.acc.GetUserAccountInfo().GetAddress() + commonAddr := common.HexToAddress(address.Hex()) + addressStr := commonAddr.Hex() + + // store group info and share the reference + group := &GroupItem{ + Name: gr.Name, + Secret: encData, + OwnerPublicKey: gr.OwnerPublicKey, + OwnerAddress: gr.OwnerAddress, + Password: gr.Password, + } + + data, err := json.Marshal(group) + if err != nil { + return nil, err + } + + ref, err := g.client.UploadBlob(data, 0, false) + if err != nil { + return nil, err + } + + err = g.acl.AddMember(groupName, addressStr, memberAddress.String(), permission) + if err != nil { + return nil, err + } + + return ref, nil +} + +func (g *Group) AcceptGroupInvite(ref []byte) error { + groups, err := g.ListGroup() + if err != nil && !errors.Is(err, f.ErrFileNotFound) { // skipcq: TCV-001 + return err + } + + // download blob + data, _, err := g.client.DownloadBlob(ref) + if err != nil { + return err + } + // unmarshall into GroupItem + group := &GroupItem{} + err = json.Unmarshal(data, group) + if err != nil { + return err + } + address := g.acc.GetUserAccountInfo().GetAddress() + commonAddr := common.HexToAddress(address.Hex()) + addressStr := commonAddr.Hex() + + // check smart contract for permission + perm, err := g.acl.GetPermission(group.Name, group.OwnerAddress, addressStr) + if err != nil { + return err + } + + if perm != aclController.PermissionRead && perm != aclController.PermissionWrite { + return ErrPermissionDenied + } + // Save in te groups list + groups.SharedGroups = append(groups.SharedGroups, *group) + + // Save the groups list + return g.store(groups) +} + +func (g *Group) RemoveMember(groupName string, memberAddress common.Address) error { + // check if group exists + groupName = strings.TrimSpace(groupName) + + groups, err := g.ListGroup() + if err != nil && !errors.Is(err, f.ErrFileNotFound) { // skipcq: TCV-001 + return err + } + if !g.checkIfPodPresent(groups, groupName) { + return ErrGroupDoesNotExist + } + address := g.acc.GetUserAccountInfo().GetAddress() + addressStr := common.HexToAddress(address.Hex()).Hex() + return g.acl.RemoveMember(groupName, addressStr, memberAddress.String()) +} + +func (g *Group) UpdatePermission(groupName string, memberAddress common.Address, permission uint8) error { + // check if group exists + groupName = strings.TrimSpace(groupName) + + groups, err := g.ListGroup() + if err != nil && !errors.Is(err, f.ErrFileNotFound) { // skipcq: TCV-001 + return err + } + if !g.checkIfPodPresent(groups, groupName) { + return ErrGroupDoesNotExist + } + address := g.acc.GetUserAccountInfo().GetAddress() + addressStr := common.HexToAddress(address.Hex()).Hex() + return g.acl.UpdatePermission(groupName, addressStr, memberAddress.String(), permission) +} + +func (g *Group) GetPermission(groupName string) (uint8, error) { + // check if group exists + groupName = strings.TrimSpace(groupName) + + groups, err := g.ListGroup() + if err != nil && !errors.Is(err, f.ErrFileNotFound) { // skipcq: TCV-001 + return 0, err + } + if !g.checkIfPodPresent(groups, groupName) { + return 0, ErrGroupDoesNotExist + } + + var gr *GroupItem + for _, group := range groups.Groups { + if group.Name == groupName { + gr = &group + break + } + } + if gr == nil { + for _, group := range groups.SharedGroups { + if group.Name == groupName { + gr = &group + break + } + } + } + if gr == nil { + return 0, ErrGroupDoesNotExist + } + + address := g.acc.GetUserAccountInfo().GetAddress() + addressStr := common.HexToAddress(address.Hex()).Hex() + return g.acl.GetPermission(groupName, gr.OwnerAddress, addressStr) +} + +func (g *Group) GetGroupMembers(groupName string) (map[string]uint8, error) { + // check if group exists + groupName = strings.TrimSpace(groupName) + + groups, err := g.ListGroup() + if err != nil && !errors.Is(err, f.ErrFileNotFound) { // skipcq: TCV-001 + return nil, err + } + if !g.checkIfPodPresent(groups, groupName) { + return nil, ErrGroupDoesNotExist + } + address := g.acc.GetUserAccountInfo().GetAddress() + addressStr := common.HexToAddress(address.Hex()).Hex() + return g.acl.GetGroupMembers(groupName, addressStr) +} + +// +//func (g *Group) GetAllGroups() (map[string]map[string]uint8, error) { +// address := g.acc.GetUserAccountInfo().GetAddress() +// addressStr := common.HexToAddress(address.Hex()).Hex() +// return g.acl.GetAllGroups(addressStr) +//} diff --git a/pkg/pod/ls.go b/pkg/pod/ls.go index c8979d93..3531d5c9 100644 --- a/pkg/pod/ls.go +++ b/pkg/pod/ls.go @@ -16,11 +16,20 @@ limitations under the License. package pod +import "fmt" + // ListPods List all the available pods belonging to a user. func (p *Pod) ListPods() ([]string, []string, error) { - podList, err := p.loadUserPods() - if err != nil { // skipcq: TCV-001 - return nil, nil, err + podList, err := p.loadUserPodsV2() + if err != nil { + podList, err = p.loadUserPods() + if err != nil { // skipcq: TCV-001 + return nil, nil, err + } + err := p.storeUserPodsV2(podList) + if err != nil { + fmt.Println("error storing podsV2", err) + } } var listPods []string @@ -38,5 +47,20 @@ func (p *Pod) ListPods() ([]string, []string, error) { // PodList lists all the available pods belonging to a user in json format. func (p *Pod) PodList() (*List, error) { - return p.loadUserPods() + //We first check if the podsV2 list is present + podList, err := p.loadUserPodsV2() + if err != nil { + // If v2 is not present we check if v1 is present + podList, err = p.loadUserPods() + if err != nil { // skipcq: TCV-001 + return nil, err + } + // we store the v1 list as v2 + err := p.storeUserPodsV2(podList) + if err != nil { + fmt.Println("error storing podsV2", err) + } + // TODO remove old v1 podList + } + return podList, nil } diff --git a/pkg/pod/new.go b/pkg/pod/new.go index 3a0efeb2..bd9995a3 100644 --- a/pkg/pod/new.go +++ b/pkg/pod/new.go @@ -17,11 +17,12 @@ limitations under the License. package pod import ( + "bytes" "encoding/hex" "encoding/json" + "io" "github.com/ethereum/go-ethereum/crypto" - "github.com/fairdatasociety/fairOS-dfs/pkg/account" c "github.com/fairdatasociety/fairOS-dfs/pkg/collection" d "github.com/fairdatasociety/fairOS-dfs/pkg/dir" @@ -31,7 +32,8 @@ import ( ) const ( - podFile = "Pods" + podFile = "Pods" + podFileV2 = "PodsV2" ) // CreatePod creates a new pod for a given user. @@ -42,20 +44,19 @@ func (p *Pod) CreatePod(podName, addressString, podPassword string) (*Info, erro } // check if pods is present and get free index - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { // skipcq: TCV-001 return nil, err } - pods := map[int]string{} sharedPods := map[string]string{} for _, pod := range podList.Pods { pods[pod.Index] = pod.Name } - for _, pod := range podList.SharedPods { sharedPods[pod.Address] = pod.Name } + var accountInfo *account.Info var fd *feed.API var file *f.File @@ -74,7 +75,7 @@ func (p *Pod) CreatePod(podName, addressString, podPassword string) (*Info, erro address := utils.HexToAddress(addressString) accountInfo.SetAddress(address) - fd = feed.New(accountInfo, p.client, p.logger) + fd = feed.New(accountInfo, p.client, p.feedCacheSize, p.feedCacheTTL, p.logger) file = f.NewFile(podName, p.client, fd, accountInfo.GetAddress(), p.tm, p.logger) dir = d.NewDirectory(podName, p.client, fd, accountInfo.GetAddress(), file, p.tm, p.logger) @@ -85,14 +86,13 @@ func (p *Pod) CreatePod(podName, addressString, podPassword string) (*Info, erro Password: podPassword, } podList.SharedPods = append(podList.SharedPods, *sharedPod) - err = p.storeUserPods(podList) + err = p.storeUserPodsV2(podList) if err != nil { // skipcq: TCV-001 return nil, err } // set the userAddress as the pod address we got from shared pod user = address - } else { // your own pod, so create a new account with private key if p.checkIfPodPresent(podList, podName) { @@ -101,22 +101,19 @@ func (p *Pod) CreatePod(podName, addressString, podPassword string) (*Info, erro if p.checkIfSharedPodPresent(podList, podName) { return nil, ErrPodAlreadyExists } - freeId, err := p.getFreeId(pods) if err != nil { // skipcq: TCV-001 return nil, err } - // create a child account for the userAddress and other data structures for the pod accountInfo, err = p.acc.CreatePodAccount(freeId, true) if err != nil { // skipcq: TCV-001 return nil, err } - - fd = feed.New(accountInfo, p.client, p.logger) + fd = feed.New(accountInfo, p.client, p.feedCacheSize, p.feedCacheTTL, p.logger) + //fd.SetUpdateTracker(p.fd.GetUpdateTracker()) file = f.NewFile(podName, p.client, fd, accountInfo.GetAddress(), p.tm, p.logger) dir = d.NewDirectory(podName, p.client, fd, accountInfo.GetAddress(), file, p.tm, p.logger) - // store the pod file pods[freeId] = podName pod := &ListItem{ @@ -125,7 +122,7 @@ func (p *Pod) CreatePod(podName, addressString, podPassword string) (*Info, erro Password: podPassword, } podList.Pods = append(podList.Pods, *pod) - err = p.storeUserPods(podList) + err = p.storeUserPodsV2(podList) if err != nil { // skipcq: TCV-001 return nil, err } @@ -162,7 +159,7 @@ func (p *Pod) loadUserPods() (*List, error) { // The userAddress pod file topic should be in the name of the userAddress account topic := utils.HashString(podFile) privKeyBytes := crypto.FromECDSA(p.acc.GetUserAccountInfo().GetPrivateKey()) - _, data, err := p.fd.GetFeedData(topic, p.acc.GetAddress(account.UserAccountIndex), []byte(hex.EncodeToString(privKeyBytes))) + _, data, err := p.fd.GetFeedData(topic, p.acc.GetAddress(account.UserAccountIndex), []byte(hex.EncodeToString(privKeyBytes)), false) if err != nil { // skipcq: TCV-001 if err.Error() != "feed does not exist or was not updated yet" { return nil, err @@ -184,23 +181,49 @@ func (p *Pod) loadUserPods() (*List, error) { return podList, nil } -func (p *Pod) storeUserPods(podList *List) error { - data, err := json.Marshal(podList) - if err != nil { - return err +func (p *Pod) loadUserPodsV2() (*List, error) { + f2 := f.NewFile("", p.client, p.fd, p.acc.GetAddress(account.UserAccountIndex), p.tm, p.logger) + topicString := utils.CombinePathAndFile("", podFileV2) + privKeyBytes := crypto.FromECDSA(p.acc.GetUserAccountInfo().GetPrivateKey()) + r, _, err := f2.Download(topicString, hex.EncodeToString(privKeyBytes)) + if err != nil { // skipcq: TCV-001 + return nil, err + } + podList := &List{ + Pods: []ListItem{}, + SharedPods: []SharedListItem{}, + } + data, err := io.ReadAll(r) + if err != nil { // skipcq: TCV-001 + return nil, err } - if len(data) > utils.MaxChunkLength { - return ErrMaximumPodLimit + if len(data) == 0 { + return podList, nil } - topic := utils.HashString(podFile) - privKeyBytes := crypto.FromECDSA(p.acc.GetUserAccountInfo().GetPrivateKey()) - _, err = p.fd.UpdateFeed(p.acc.GetAddress(account.UserAccountIndex), topic, data, []byte(hex.EncodeToString(privKeyBytes))) + err = json.Unmarshal(data, podList) if err != nil { // skipcq: TCV-001 + return nil, err + } + + return podList, nil +} + +func (p *Pod) storeUserPodsV2(podList *List) error { + + data, err := json.Marshal(podList) + if err != nil { return err } - return nil + + // store data as file and get metadata + // This is a very hacky way to store pod data, but it works for now + // We create a new file object with the user account address and upload the data + // We use the user private key to encrypt data. + f2 := f.NewFile("", p.client, p.fd, p.acc.GetAddress(account.UserAccountIndex), p.tm, p.logger) + privKeyBytes := crypto.FromECDSA(p.acc.GetUserAccountInfo().GetPrivateKey()) + return f2.Upload(bytes.NewReader(data), podFileV2, int64(len(data)), f.MinBlockSize, 0, "/", "gzip", hex.EncodeToString(privKeyBytes)) } func (*Pod) getFreeId(pods map[int]string) (int, error) { @@ -235,7 +258,7 @@ func (*Pod) checkIfSharedPodPresent(pods *List, podName string) bool { } func (p *Pod) getPodIndex(podName string) (podIndex int, err error) { - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { return -1, err } // skipcq: TCV-001 diff --git a/pkg/pod/open.go b/pkg/pod/open.go index 0cec5aef..c83cee87 100644 --- a/pkg/pod/open.go +++ b/pkg/pod/open.go @@ -32,8 +32,12 @@ import ( // it loads all the data structures related to the pod. Also, it syncs all the // files and directories under this pod from the Swarm network. func (p *Pod) OpenPod(podName string) (*Info, error) { + pi, _, _ := p.GetPodInfoFromPodMap(podName) + if pi != nil { + return pi, nil + } // check if pods is present and get the index of the pod - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { // skipcq: TCV-001 return nil, err } @@ -45,6 +49,7 @@ func (p *Pod) OpenPod(podName string) (*Info, error) { sharedPodType = true } } + var ( podPassword string accountInfo *account.Info @@ -64,7 +69,7 @@ func (p *Pod) OpenPod(podName string) (*Info, error) { address := utils.HexToAddress(addressString) accountInfo.SetAddress(address) - fd = feed.New(accountInfo, p.client, p.logger) + fd = feed.New(accountInfo, p.client, p.feedCacheSize, p.feedCacheTTL, p.logger) file = f.NewFile(podName, p.client, fd, accountInfo.GetAddress(), p.tm, p.logger) dir = d.NewDirectory(podName, p.client, fd, accountInfo.GetAddress(), file, p.tm, p.logger) @@ -82,14 +87,16 @@ func (p *Pod) OpenPod(podName string) (*Info, error) { if err != nil { // skipcq: TCV-001 return nil, err } - - fd = feed.New(accountInfo, p.client, p.logger) + fd = feed.New(accountInfo, p.client, p.feedCacheSize, p.feedCacheTTL, p.logger) + //_, err = tracker.InitFeedsTracker(accountInfo.GetAddress(), podName, podPassword, fd, p.client, p.logger) + //if err != nil { + // p.logger.Errorf("error initializing feeds tracker: %v", err) + //} file = f.NewFile(podName, p.client, fd, accountInfo.GetAddress(), p.tm, p.logger) dir = d.NewDirectory(podName, p.client, fd, accountInfo.GetAddress(), file, p.tm, p.logger) user = p.acc.GetAddress(index) } - kvStore := c.NewKeyValueStore(podName, fd, accountInfo, user, p.client, p.logger) docStore := c.NewDocumentStore(podName, fd, accountInfo, user, file, p.tm, p.client, p.logger) @@ -105,7 +112,6 @@ func (p *Pod) OpenPod(podName string) (*Info, error) { kvStore: kvStore, docStore: docStore, } - p.addPodToPodMap(podName, podInfo) if !sharedPodType { err = podInfo.GetDirectory().AddRootDir(podInfo.GetPodName(), podInfo.GetPodPassword(), podInfo.GetPodAddress(), podInfo.GetFeed()) @@ -113,7 +119,6 @@ func (p *Pod) OpenPod(podName string) (*Info, error) { return nil, err } } - return podInfo, nil } @@ -122,7 +127,7 @@ func (p *Pod) OpenFromShareInfo(si *ShareInfo) (*Info, error) { address := utils.HexToAddress(si.Address) accountInfo.SetAddress(address) - fd := feed.New(accountInfo, p.client, p.logger) + fd := feed.New(accountInfo, p.client, p.feedCacheSize, p.feedCacheTTL, p.logger) file := f.NewFile(si.PodName, p.client, fd, accountInfo.GetAddress(), p.tm, p.logger) dir := d.NewDirectory(si.PodName, p.client, fd, accountInfo.GetAddress(), file, p.tm, p.logger) @@ -156,7 +161,7 @@ func (p *Pod) OpenFromShareInfo(si *ShareInfo) (*Info, error) { // files and directories under this pod from the Swarm network. func (p *Pod) OpenPodAsync(ctx context.Context, podName string) (*Info, error) { // check if pods is present and get the index of the pod - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { // skipcq: TCV-001 return nil, err } @@ -189,7 +194,7 @@ func (p *Pod) OpenPodAsync(ctx context.Context, podName string) (*Info, error) { address := utils.HexToAddress(addressString) accountInfo.SetAddress(address) - fd = feed.New(accountInfo, p.client, p.logger) + fd = feed.New(accountInfo, p.client, p.feedCacheSize, p.feedCacheTTL, p.logger) file = f.NewFile(podName, p.client, fd, accountInfo.GetAddress(), p.tm, p.logger) dir = d.NewDirectory(podName, p.client, fd, accountInfo.GetAddress(), file, p.tm, p.logger) @@ -208,7 +213,8 @@ func (p *Pod) OpenPodAsync(ctx context.Context, podName string) (*Info, error) { return nil, err } - fd = feed.New(accountInfo, p.client, p.logger) + fd = feed.New(accountInfo, p.client, p.feedCacheSize, p.feedCacheTTL, p.logger) + //fd.SetUpdateTracker(p.fd.GetUpdateTracker()) file = f.NewFile(podName, p.client, fd, accountInfo.GetAddress(), p.tm, p.logger) dir = d.NewDirectory(podName, p.client, fd, accountInfo.GetAddress(), file, p.tm, p.logger) diff --git a/pkg/pod/pod.go b/pkg/pod/pod.go index fcb30a24..1eb69813 100644 --- a/pkg/pod/pod.go +++ b/pkg/pod/pod.go @@ -19,6 +19,7 @@ package pod import ( "fmt" "sync" + "time" "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager" @@ -38,14 +39,16 @@ const ( // Pod is the main struct which acts on pods type Pod struct { - fd *feed.API - acc *account.Account - client blockstore.Client - podMap map[string]*Info // podName -> dir - podMu *sync.RWMutex - logger logging.Logger - tm taskmanager.TaskManagerGO - sm subscriptionManager.SubscriptionManager + fd *feed.API + acc *account.Account + client blockstore.Client + podMap map[string]*Info // podName -> dir + podMu *sync.RWMutex + logger logging.Logger + tm taskmanager.TaskManagerGO + sm subscriptionManager.SubscriptionManager + feedCacheSize int + feedCacheTTL time.Duration } // ListItem defines the structure for pod item @@ -69,17 +72,18 @@ type List struct { } // NewPod creates the main pod object which has all the methods related to the pods. -func NewPod(client blockstore.Client, feed *feed.API, account *account.Account, - m taskmanager.TaskManagerGO, sm subscriptionManager.SubscriptionManager, logger logging.Logger) *Pod { +func NewPod(client blockstore.Client, feed *feed.API, account *account.Account, m taskmanager.TaskManagerGO, sm subscriptionManager.SubscriptionManager, feedCacheSize int, feedCacheTTL time.Duration, logger logging.Logger) *Pod { return &Pod{ - fd: feed, - acc: account, - client: client, - podMap: make(map[string]*Info), - podMu: &sync.RWMutex{}, - logger: logger, - tm: m, - sm: sm, + fd: feed, + acc: account, + client: client, + podMap: make(map[string]*Info), + podMu: &sync.RWMutex{}, + logger: logger, + tm: m, + sm: sm, + feedCacheSize: feedCacheSize, + feedCacheTTL: feedCacheTTL, } } diff --git a/pkg/pod/sharing.go b/pkg/pod/sharing.go index ee127bae..ca8bc40a 100644 --- a/pkg/pod/sharing.go +++ b/pkg/pod/sharing.go @@ -37,7 +37,7 @@ type ShareInfo struct { // required to import this pod. func (p *Pod) PodShare(podName, sharedPodName string) (string, error) { // check if pods is present and get the index of the pod - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { // skipcq: TCV-001 return "", err } @@ -84,7 +84,7 @@ func (p *Pod) PodShare(podName, sharedPodName string) (string, error) { // GetPodSharingInfo returns the raw shareInfo func (p *Pod) GetPodSharingInfo(podName string) (*ShareInfo, error) { // check if pods is present and get the index of the pod - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { // skipcq: TCV-001 return nil, err } diff --git a/pkg/pod/stat.go b/pkg/pod/stat.go index 6723b028..b6313406 100644 --- a/pkg/pod/stat.go +++ b/pkg/pod/stat.go @@ -33,7 +33,7 @@ func (p *Pod) PodStat(podName string) (*Stat, error) { PodAddress: podInfo.userAddress.String(), }, nil } - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { return nil, err } diff --git a/pkg/pod/subscription.go b/pkg/pod/subscription.go index b4f4e1cd..013ed418 100644 --- a/pkg/pod/subscription.go +++ b/pkg/pod/subscription.go @@ -17,7 +17,7 @@ import ( // ListPodInMarketplace will save the pod info in the subscriptionManager smart contract with its owner and price // we keep the pod info in the smart contract, with a `list` flag func (p *Pod) ListPodInMarketplace(podName, title, desc, thumbnail string, price uint64, daysValid uint16, category, nameHash [32]byte) error { - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { // skipcq: TCV-001 return err } diff --git a/pkg/pod/utils.go b/pkg/pod/utils.go index ac2d342f..3fb7e02c 100644 --- a/pkg/pod/utils.go +++ b/pkg/pod/utils.go @@ -31,7 +31,7 @@ func (p *Pod) IsOwnPodPresent(podName string) bool { return false } // check if pods is present and get free index - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { // skipcq: TCV-001 return false } @@ -48,7 +48,7 @@ func (p *Pod) IsPodPresent(podName string) bool { return false } // check if pods is present and get free index - podList, err := p.loadUserPods() + podList, err := p.PodList() if err != nil { // skipcq: TCV-001 return false } diff --git a/pkg/subscriptionManager/rpc/manager.go b/pkg/subscriptionManager/rpc/manager.go index eb0376f9..dff4e548 100644 --- a/pkg/subscriptionManager/rpc/manager.go +++ b/pkg/subscriptionManager/rpc/manager.go @@ -94,7 +94,7 @@ func (c *Client) AddPodToMarketplace(podAddress, owner common.Address, pod, titl var a [32]byte copy(a[:], ref) - tx, err := c.datahub.ListSub(opts, nameHash, a, new(big.Int).SetUint64(price), category, podAddress, daysValid) + tx, err := c.datahub.ListSub(opts, nameHash, a, new(big.Int).SetUint64(price), category, podAddress, new(big.Int).SetUint64(uint64(daysValid))) if err != nil { return err } diff --git a/pkg/subscriptionManager/rpc/mock/rpc.go b/pkg/subscriptionManager/rpc/mock/rpc.go index 5121ec2f..bfc5fd4c 100644 --- a/pkg/subscriptionManager/rpc/mock/rpc.go +++ b/pkg/subscriptionManager/rpc/mock/rpc.go @@ -57,9 +57,9 @@ func (s *SubscriptionManager) AddPodToMarketplace(podAddress, owner common.Addre Price: new(big.Int).SetUint64(price), Active: true, Earned: nil, - Bids: 0, - Sells: 0, - Reports: 0, + Bids: new(big.Int).SetUint64(0), + Sells: new(big.Int).SetUint64(0), + Reports: new(big.Int).SetUint64(0), } s.lock.Lock() defer s.lock.Unlock() diff --git a/pkg/test/close_test.go b/pkg/test/close_test.go index 8c64fb4f..568f7558 100644 --- a/pkg/test/close_test.go +++ b/pkg/test/close_test.go @@ -22,29 +22,37 @@ import ( "testing" "time" - mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" - - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" - - "github.com/plexsysio/taskmanager" - + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestClose(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) @@ -52,7 +60,7 @@ func TestClose(t *testing.T) { sm := mock2.NewMockSubscriptionManager() - pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, logger) + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) podName1 := "test1" t.Run("close-pod", func(t *testing.T) { @@ -77,17 +85,17 @@ func TestClose(t *testing.T) { gotPodInfo, _, err = pod1.GetPodInfo(podName1) if err != nil { - t.Fatalf("pod should be open") + t.Fatalf("pod should be open %s\n", err) } if gotPodInfo == nil { t.Fatalf("pod should be open") } dirObject := gotPodInfo.GetDirectory() - dirInode1 := dirObject.GetInode(podPassword, "/parentDir/subDir1") + dirInode1, _ := dirObject.GetInode(podPassword, "/parentDir/subDir1") if dirInode1 == nil { t.Fatalf("dir should nil be nil") } - dirInode2 := dirObject.GetInode(podPassword, "/parentDir/subDir2") + dirInode2, _ := dirObject.GetInode(podPassword, "/parentDir/subDir2") if dirInode2 == nil { t.Fatalf("dir should nil be nil") } diff --git a/pkg/test/del_test.go b/pkg/test/del_test.go index 731dbfef..6972c89f 100644 --- a/pkg/test/del_test.go +++ b/pkg/test/del_test.go @@ -24,37 +24,45 @@ import ( "testing" "time" - mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" - - "github.com/fairdatasociety/fairOS-dfs/pkg/utils" - - "github.com/plexsysio/taskmanager" - + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/collection" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestPodDelete(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() sm := mock2.NewMockSubscriptionManager() - pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, logger) + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) podName1 := "test1" podName2 := "test2" diff --git a/pkg/test/delete_test.go b/pkg/test/delete_test.go index 558436eb..f4b3b20a 100644 --- a/pkg/test/delete_test.go +++ b/pkg/test/delete_test.go @@ -23,18 +23,28 @@ import ( "testing" "time" - mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" - + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/ensm/eth/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/user" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestDelete(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) @@ -44,7 +54,7 @@ func TestDelete(t *testing.T) { t.Run("delete-user", func(t *testing.T) { ens := mock2.NewMockNamespaceManager() // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) sr, err := userObject.CreateNewUserV2("user1", "password1twelve", "", "", tm, sm) if err != nil { t.Fatal(err) diff --git a/pkg/test/fork_test.go b/pkg/test/fork_test.go index ed9fbedc..3a35f20a 100644 --- a/pkg/test/fork_test.go +++ b/pkg/test/fork_test.go @@ -22,22 +22,31 @@ import ( "testing" "time" - "github.com/fairdatasociety/fairOS-dfs/pkg/file" - - mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" - + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestFork(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -47,10 +56,10 @@ func TestFork(t *testing.T) { defer func() { _ = tm.Stop(context.Background()) }() - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) sm := mock2.NewMockSubscriptionManager() - pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, logger) + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) podName1 := "test1" t.Run("fork-pod", func(t *testing.T) { @@ -94,7 +103,7 @@ func TestFork(t *testing.T) { // validate if the directory and files are synced dirObject := gotInfo.GetDirectory() - dirInode1 := dirObject.GetInode(podPassword, "/parentDir/subDir1") + dirInode1, _ := dirObject.GetInode(podPassword, "/parentDir/subDir1") if dirInode1 == nil { t.Fatalf("invalid dir entry") } @@ -104,7 +113,7 @@ func TestFork(t *testing.T) { if dirInode1.Meta.Name != "subDir1" { t.Fatalf("invalid dir entry") } - dirInode2 := dirObject.GetInode(podPassword, "/parentDir/subDir2") + dirInode2, _ := dirObject.GetInode(podPassword, "/parentDir/subDir2") if dirInode2 == nil { t.Fatalf("invalid dir entry") } diff --git a/pkg/test/group_new_test.go b/pkg/test/group_new_test.go new file mode 100644 index 00000000..70230234 --- /dev/null +++ b/pkg/test/group_new_test.go @@ -0,0 +1,577 @@ +/* +Copyright © 2020 FairOS Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +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. +*/ + +package test_test + +import ( + "errors" + "fmt" + "io" + "testing" + + "github.com/ethereum/go-ethereum/common" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/acl/acl" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/feed" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" + "github.com/sirupsen/logrus" +) + +func TestGroupNew(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + + t.Run("create-first-group", func(t *testing.T) { + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl := acl.NewACL(mockClient, fd, logger) + group := pod.NewGroup(mockClient, fd, acc, mockAcl, logger) + groupName1, _ := utils.GetRandString(10) + _, err = group.CreateGroup(groupName1) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + + groups, err := group.ListGroup() + if err != nil { + t.Fatalf("error getting groups") + } + + if len(groups.Groups) != 1 { + t.Fatalf("length of groups is not 1") + } + + _, err = group.OpenGroup(groupName1) + if err != nil { + t.Fatalf("error opening group %s: %s", groupName1, err.Error()) + } + }) + + t.Run("create-second-group", func(t *testing.T) { + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl := acl.NewACL(mockClient, fd, logger) + + group := pod.NewGroup(mockClient, fd, acc, mockAcl, logger) + groupName1, _ := utils.GetRandString(10) + groupName2, _ := utils.GetRandString(10) + _, err = group.CreateGroup(groupName1) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + _, err = group.CreateGroup(groupName2) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName2, err.Error()) + } + _, err = group.ListGroup() + if err != nil { + t.Fatalf("error getting groups") + } + + _, err = group.OpenGroup(groupName2) + if err != nil { + t.Fatalf("error opening group %s: %s", groupName2, err.Error()) + } + }) + + t.Run("group-file-upload", func(t *testing.T) { + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl := acl.NewACL(mockClient, fd, logger) + group := pod.NewGroup(mockClient, fd, acc, mockAcl, logger) + groupName1, _ := utils.GetRandString(10) + _, err = group.CreateGroup(groupName1) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + + _, err = group.ListGroup() + if err != nil { + t.Fatalf("error getting groups") + } + + g, err := group.OpenGroup(groupName1) + if err != nil { + t.Fatalf("error opening group %s: %s", groupName1, err.Error()) + } + + maxfiles := 100 + filePath := "/" + for i := 1; i <= maxfiles; i++ { + fileName, _ := utils.GetRandString(100) + compression := "" + fileSize := int64(1000) + blockSize := file.MinBlockSize + _, err = uploadFile(t, g.GetFile(), filePath, fileName, compression, g.GetPodPassword(), fileSize, blockSize) + if err != nil { + t.Fatal(err) + } + err = g.GetDirectory().AddEntryToDir("/", g.GetPodPassword(), fileName, true) + if err != nil { + t.Fatal(i, err) + } + } + + dirInode, err := g.GetDirectory().GetInode(g.GetPodPassword(), filePath) + if err != nil { + t.Fatal(err) + } + if len(dirInode.FileOrDirNames) != maxfiles { + t.Fatal("files not present") + } + }) + + t.Run("group-member-add", func(t *testing.T) { + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl := acl.NewACL(mockClient, fd, logger) + group := pod.NewGroup(mockClient, fd, acc, mockAcl, logger) + groupName1, _ := utils.GetRandString(10) + fmt.Println("group name", groupName1) + _, err = group.CreateGroup(groupName1) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + + _, err = group.ListGroup() + if err != nil { + t.Fatalf("error getting groups") + } + + g, err := group.OpenGroup(groupName1) + if err != nil { + t.Fatalf("error opening group %s: %s", groupName1, err.Error()) + } + maxfiles := 10 + filePath := "/" + for i := 1; i <= maxfiles; i++ { + fileName, _ := utils.GetRandString(100) + compression := "" + fileSize := int64(1000) + blockSize := file.MinBlockSize + _, err = uploadFile(t, g.GetFile(), filePath, fileName, compression, g.GetPodPassword(), fileSize, blockSize) + if err != nil { + t.Fatal(err) + } + err = g.GetDirectory().AddEntryToDir("/", g.GetPodPassword(), fileName, true) + if err != nil { + t.Fatal(i, err) + } + } + + acc2 := account.New(logger) + _, _, err = acc2.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + addr := acc2.GetUserAccountInfo().GetAddress() + addrStr := addr.Hex() + ref, err := group.AddMember(groupName1, common.HexToAddress(addrStr), acc2.GetUserAccountInfo().GetPublicKey(), acl.PermissionWrite) + if err != nil { + t.Fatal(err) + } + fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, -1, 0, logger) + + group2 := pod.NewGroup(mockClient, fd2, acc2, mockAcl, logger) + err = group2.AcceptGroupInvite(ref) + if err != nil { + t.Fatal(err) + } + + gi, err := group2.OpenGroup(groupName1) + if err != nil { + t.Fatal(err) + } + dirInode, err := gi.GetDirectory().GetInode(gi.GetPodPassword(), filePath) + if err != nil { + t.Fatal(err) + } + if len(dirInode.FileOrDirNames) != maxfiles { + t.Fatal("files not present") + } + }) + + t.Run("group-member-check-no-permission", func(t *testing.T) { + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl := acl.NewACL(mockClient, fd, logger) + group := pod.NewGroup(mockClient, fd, acc, mockAcl, logger) + groupName1, _ := utils.GetRandString(10) + _, err = group.CreateGroup(groupName1) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + + _, err = group.ListGroup() + if err != nil { + t.Fatalf("error getting groups") + } + + _, err = group.OpenGroup(groupName1) + if err != nil { + t.Fatalf("error opening group %s: %s", groupName1, err.Error()) + } + + acc2 := account.New(logger) + _, _, err = acc2.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + + fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl2 := acl.NewACL(mockClient, fd2, logger) + + group2 := pod.NewGroup(mockClient, fd2, acc2, mockAcl2, logger) + groupName2, _ := utils.GetRandString(10) + _, err = group2.CreateGroup(groupName2) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + + _, err = group2.OpenGroup(groupName2) + if err != nil { + t.Fatal(err) + } + perm, err := group2.GetPermission(groupName2) + if err != nil { + t.Fatal(err) + } + fmt.Println("permission", perm) + if perm != acl.PermissionWrite { + t.Fatal("permission does not match") + } + + perm1, err := group.GetPermission(groupName1) + if err != nil { + t.Fatal(err) + } + if perm1 != acl.PermissionWrite { + t.Fatal("permission does not match") + } + }) + + t.Run("group-member-check-permission", func(t *testing.T) { + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl := acl.NewACL(mockClient, fd, logger) + group := pod.NewGroup(mockClient, fd, acc, mockAcl, logger) + groupName1, _ := utils.GetRandString(10) + _, err = group.CreateGroup(groupName1) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + + _, err = group.ListGroup() + if err != nil { + t.Fatalf("error getting groups") + } + + _, err = group.OpenGroup(groupName1) + if err != nil { + t.Fatalf("error opening group %s: %s", groupName1, err.Error()) + } + + acc2 := account.New(logger) + _, _, err = acc2.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + addr := acc2.GetUserAccountInfo().GetAddress() + addrStr := addr.Hex() + ref, err := group.AddMember(groupName1, common.HexToAddress(addrStr), acc2.GetUserAccountInfo().GetPublicKey(), acl.PermissionRead) + if err != nil { + t.Fatal(err) + } + fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, -1, 0, logger) + + group2 := pod.NewGroup(mockClient, fd2, acc2, mockAcl, logger) + err = group2.AcceptGroupInvite(ref) + if err != nil { + t.Fatal(err) + } + + _, err = group2.OpenGroup(groupName1) + if err != nil { + t.Fatal(err) + } + + perm, err := group2.GetPermission(groupName1) + if err != nil { + t.Fatal(err) + } + if perm != acl.PermissionRead { + t.Fatal("permission not read") + } + + err = group.UpdatePermission(groupName1, common.HexToAddress(addrStr), acl.PermissionWrite) + if err != nil { + t.Fatal(err) + } + perm, err = group2.GetPermission(groupName1) + if err != nil { + t.Fatal(err) + } + if perm != acl.PermissionWrite { + t.Fatal("permission not write") + } + }) + + t.Run("group-member-upload-files", func(t *testing.T) { + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl := acl.NewACL(mockClient, fd, logger) + group := pod.NewGroup(mockClient, fd, acc, mockAcl, logger) + groupName1, _ := utils.GetRandString(10) + _, err = group.CreateGroup(groupName1) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + + _, err = group.ListGroup() + if err != nil { + t.Fatalf("error getting groups") + } + + _, err = group.OpenGroup(groupName1) + if err != nil { + t.Fatalf("error opening group %s: %s", groupName1, err.Error()) + } + + acc2 := account.New(logger) + _, _, err = acc2.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + addr := acc2.GetUserAccountInfo().GetAddress() + addrStr := addr.Hex() + ref, err := group.AddMember(groupName1, common.HexToAddress(addrStr), acc2.GetUserAccountInfo().GetPublicKey(), acl.PermissionRead) + if err != nil { + t.Fatal(err) + } + fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, -1, 0, logger) + + group2 := pod.NewGroup(mockClient, fd2, acc2, mockAcl, logger) + err = group2.AcceptGroupInvite(ref) + if err != nil { + t.Fatal(err) + } + + g, err := group2.OpenGroup(groupName1) + if err != nil { + t.Fatal(err) + } + + fileName, _ := utils.GetRandString(100) + compression := "" + fileSize := int64(1000) + blockSize := file.MinBlockSize + _, err = uploadFile(t, g.GetFile(), "/", fileName, compression, g.GetPodPassword(), fileSize, blockSize) + if !errors.Is(err, feed.ErrReadOnlyFeed) { + t.Fatal(err) + } + + err = group.UpdatePermission(groupName1, common.HexToAddress(addrStr), acl.PermissionWrite) + if err != nil { + t.Fatal(err) + } + + // reopen the group to reload feed with new permission + err = group2.CloseGroup(groupName1) + if err != nil { + t.Fatal(err) + } + g, err = group2.OpenGroup(groupName1) + if err != nil { + t.Fatal(err) + } + + _, err = uploadFile(t, g.GetFile(), "/", fileName, compression, g.GetPodPassword(), fileSize, blockSize) + if err != nil { + t.Fatal(err) + } + err = g.GetDirectory().AddEntryToDir("/", g.GetPodPassword(), fileName, true) + if err != nil { + t.Fatal(err) + } + + dirInode, err := g.GetDirectory().GetInode(g.GetPodPassword(), "/") + if err != nil { + t.Fatal(err) + } + if len(dirInode.FileOrDirNames) != 1 { + t.Fatal("files not present") + } + if dirInode.FileOrDirNames[0] != "_F_"+fileName { + t.Fatal("file name not correct") + } + }) + + t.Run("group-member-remove", func(t *testing.T) { + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl := acl.NewACL(mockClient, fd, logger) + group := pod.NewGroup(mockClient, fd, acc, mockAcl, logger) + groupName1, _ := utils.GetRandString(10) + _, err = group.CreateGroup(groupName1) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + + _, err = group.ListGroup() + if err != nil { + t.Fatalf("error getting groups") + } + + _, err = group.OpenGroup(groupName1) + if err != nil { + t.Fatalf("error opening group %s: %s", groupName1, err.Error()) + } + + acc2 := account.New(logger) + _, _, err = acc2.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + addr := acc2.GetUserAccountInfo().GetAddress() + addrStr := addr.Hex() + ref, err := group.AddMember(groupName1, common.HexToAddress(addrStr), acc2.GetUserAccountInfo().GetPublicKey(), acl.PermissionRead) + if err != nil { + t.Fatal(err) + } + fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, -1, 0, logger) + + group2 := pod.NewGroup(mockClient, fd2, acc2, mockAcl, logger) + err = group2.AcceptGroupInvite(ref) + if err != nil { + t.Fatal(err) + } + + err = group.RemoveMember(groupName1, common.HexToAddress(addrStr)) + if err != nil { + t.Fatal(err) + } + _, err = group2.OpenGroup(groupName1) + if !errors.Is(err, pod.ErrPermissionDenied) { + t.Fatal(err) + } + }) + + t.Run("group-remove", func(t *testing.T) { + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl := acl.NewACL(mockClient, fd, logger) + group := pod.NewGroup(mockClient, fd, acc, mockAcl, logger) + groupName1, _ := utils.GetRandString(10) + _, err = group.CreateGroup(groupName1) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + + _, err = group.ListGroup() + if err != nil { + t.Fatalf("error getting groups") + } + + _, err = group.OpenGroup(groupName1) + if err != nil { + t.Fatalf("error opening group %s: %s", groupName1, err.Error()) + } + + acc2 := account.New(logger) + _, _, err = acc2.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + addr := acc2.GetUserAccountInfo().GetAddress() + addrStr := addr.Hex() + ref, err := group.AddMember(groupName1, common.HexToAddress(addrStr), acc2.GetUserAccountInfo().GetPublicKey(), acl.PermissionRead) + if err != nil { + t.Fatal(err) + } + fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, -1, 0, logger) + + group2 := pod.NewGroup(mockClient, fd2, acc2, mockAcl, logger) + err = group2.AcceptGroupInvite(ref) + if err != nil { + t.Fatal(err) + } + + err = group.RemoveGroup(groupName1) + if err != nil { + t.Fatal(err) + } + _, err = group2.OpenGroup(groupName1) + if !errors.Is(err, pod.ErrPermissionDenied) { + t.Fatal(err) + } + _, err = group2.OpenGroup(groupName1) + if !errors.Is(err, pod.ErrGroupDoesNotExist) { + t.Fatal(err) + } + }) + + t.Run("group-add-multiple-member", func(t *testing.T) { + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + mockAcl := acl.NewACL(mockClient, fd, logger) + group := pod.NewGroup(mockClient, fd, acc, mockAcl, logger) + groupName1, _ := utils.GetRandString(10) + _, err = group.CreateGroup(groupName1) + if err != nil { + t.Fatalf("error creating group %s: %s", groupName1, err.Error()) + } + + _, err = group.ListGroup() + if err != nil { + t.Fatalf("error getting groups") + } + _, err = group.OpenGroup(groupName1) + if err != nil { + t.Fatalf("error opening group %s: %s", groupName1, err.Error()) + } + userCount := 10 + for i := 0; i < userCount; i++ { + acc2 := account.New(logger) + _, _, err = acc2.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + addr := acc2.GetUserAccountInfo().GetAddress() + addrStr := addr.Hex() + _, err = group.AddMember(groupName1, common.HexToAddress(addrStr), acc2.GetUserAccountInfo().GetPublicKey(), acl.PermissionWrite) + if err != nil { + t.Fatal(err) + } + } + users, err := group.GetGroupMembers(groupName1) + if err != nil { + t.Fatal(err) + } + if len(users) != userCount+1 { + t.Fatal("users not added") + } + }) + +} diff --git a/pkg/test/integration_test.go b/pkg/test/integration_test.go index 3afaa608..22d23b27 100644 --- a/pkg/test/integration_test.go +++ b/pkg/test/integration_test.go @@ -8,6 +8,10 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" "github.com/fairdatasociety/fairOS-dfs/cmd/common" @@ -34,11 +38,18 @@ func randStringRunes(n int) string { } func TestLiteUser(t *testing.T) { - mockClient := mock.NewMockBeeClient() ens := mock2.NewMockNamespaceManager() - logger := logging.New(io.Discard, logrus.ErrorLevel) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) - users := user.NewUsers(mockClient, ens, logger) + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + + users := user.NewUsers(mockClient, ens, -1, 0, logger) dfsApi := dfs.NewMockDfsAPI(mockClient, users, logger) defer dfsApi.Close() t.Run("signup-login-pod-dir-file-rename", func(t *testing.T) { @@ -126,13 +137,13 @@ func TestLiteUser(t *testing.T) { for _, v := range entries { if v.isDir { - err = dfsApi.Mkdir(podRequest.PodName, v.path, sessionId, 0) + err = dfsApi.Mkdir(podRequest.PodName, v.path, sessionId, 0, false) if err != nil { t.Fatal(err) } } else { reader := &io.LimitedReader{R: rand.Reader, N: v.size} - err = dfsApi.UploadFile(podRequest.PodName, filepath.Base(v.path), sessionId, v.size, reader, filepath.Dir(v.path), "", file.MinBlockSize, 0, false) + err = dfsApi.UploadFile(podRequest.PodName, filepath.Base(v.path), sessionId, v.size, reader, filepath.Dir(v.path), "", file.MinBlockSize, 0, false, false) if err != nil { t.Fatal(err) } @@ -141,12 +152,12 @@ func TestLiteUser(t *testing.T) { for _, v := range entries { if v.isDir { - _, err := dfsApi.DirectoryStat(podRequest.PodName, v.path, sessionId) + _, err := dfsApi.DirectoryStat(podRequest.PodName, v.path, sessionId, false) if err != nil { t.Fatal("DirectoryStat failed for ", v.path, err) } } else { - _, err := dfsApi.FileStat(podRequest.PodName, v.path, sessionId) + _, err := dfsApi.FileStat(podRequest.PodName, v.path, sessionId, false) if err != nil { t.Fatal(err) } @@ -189,12 +200,12 @@ func TestLiteUser(t *testing.T) { } for _, v := range renames { if v.isDir { - err = dfsApi.RenameDir(podRequest.PodName, v.oldPath, v.newPath, sessionId) + err = dfsApi.RenameDir(podRequest.PodName, v.oldPath, v.newPath, sessionId, false) if err != nil { t.Fatal(err) } } else { - err = dfsApi.RenameFile(podRequest.PodName, v.oldPath, v.newPath, sessionId) + err = dfsApi.RenameFile(podRequest.PodName, v.oldPath, v.newPath, sessionId, false) if err != nil { t.Fatal(err) } @@ -262,20 +273,20 @@ func TestLiteUser(t *testing.T) { } for _, v := range newEntries { if v.isDir { - _, err := dfsApi.DirectoryStat(podRequest.PodName, v.path, sessionId) + _, err := dfsApi.DirectoryStat(podRequest.PodName, v.path, sessionId, false) if err != nil { t.Fatal(err) } } else { - _, err := dfsApi.FileStat(podRequest.PodName, v.path, sessionId) + _, err := dfsApi.FileStat(podRequest.PodName, v.path, sessionId, false) if err != nil { t.Fatal(err) } } } - + //pi.GetFeed().CommitFeeds() err = dfsApi.LogoutUser(sessionId) if err != nil { t.Fatal(err) @@ -297,12 +308,12 @@ func TestLiteUser(t *testing.T) { } for _, v := range newEntries { if v.isDir { - _, err := dfsApi.DirectoryStat(podRequest.PodName, v.path, sessionId) + _, err := dfsApi.DirectoryStat(podRequest.PodName, v.path, sessionId, false) if err != nil { t.Fatal(err) } } else { - _, err := dfsApi.FileStat(podRequest.PodName, v.path, sessionId) + _, err := dfsApi.FileStat(podRequest.PodName, v.path, sessionId, false) if err != nil { t.Fatal(err, v.path) } diff --git a/pkg/test/lite_test.go b/pkg/test/lite_test.go index b9180f58..f52b396e 100644 --- a/pkg/test/lite_test.go +++ b/pkg/test/lite_test.go @@ -7,18 +7,28 @@ import ( "testing" "time" - mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" - + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/ensm/eth/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/user" "github.com/plexsysio/taskmanager" + "github.com/sirupsen/logrus" ) func TestLite(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) @@ -29,7 +39,7 @@ func TestLite(t *testing.T) { ens := mock2.NewMockNamespaceManager() // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) _, _, _, err := userObject.LoadLiteUser("", "password1", "", "", tm, sm) if !errors.Is(err, user.ErrInvalidUserName) { t.Fatal(err) @@ -40,7 +50,7 @@ func TestLite(t *testing.T) { ens := mock2.NewMockNamespaceManager() // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) mnemonic, _, ui, err := userObject.LoadLiteUser("user1", "password1", "", "", tm, sm) if err != nil { t.Fatal(err) diff --git a/pkg/test/login_test.go b/pkg/test/login_test.go index 4d1cd8e9..fd09d672 100644 --- a/pkg/test/login_test.go +++ b/pkg/test/login_test.go @@ -24,8 +24,13 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" + "github.com/sirupsen/logrus" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/ensm/eth/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" @@ -36,8 +41,15 @@ import ( ) func TestLogin(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) @@ -47,7 +59,7 @@ func TestLogin(t *testing.T) { t.Run("login-user", func(t *testing.T) { ens := mock2.NewMockNamespaceManager() // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) sr, err := userObject.CreateNewUserV2("7e4567e7cb003804992eef11fd5c757275a4c", "password1twelve", "", "", tm, sm) if err != nil { t.Fatal(err) @@ -102,7 +114,7 @@ func TestLogin(t *testing.T) { user1 := "multicredtester" pass := "password1password1" // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) sr, err := userObject.CreateNewUserV2(user1, pass, "", "", tm, sm) if err != nil { t.Fatal(err) @@ -161,7 +173,7 @@ func TestLogin(t *testing.T) { ens := mock2.NewMockNamespaceManager() user1 := "multicredtester" // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) pass := "password1password1" sr, err := userObject.CreateNewUserV2(user1, pass, "", "", tm, sm) if err != nil { diff --git a/pkg/test/logout_test.go b/pkg/test/logout_test.go index e6f42de0..582f04d0 100644 --- a/pkg/test/logout_test.go +++ b/pkg/test/logout_test.go @@ -23,10 +23,15 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" + "github.com/sirupsen/logrus" "github.com/plexsysio/taskmanager" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/ensm/eth/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" @@ -34,8 +39,15 @@ import ( ) func TestLogout(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) @@ -46,7 +58,7 @@ func TestLogout(t *testing.T) { ens := mock2.NewMockNamespaceManager() // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) sr, err := userObject.CreateNewUserV2("user1", "password1twelve", "", "", tm, sm) if err != nil { t.Fatal(err) diff --git a/pkg/test/ls_test.go b/pkg/test/ls_test.go index b64b9b73..3baf02b8 100644 --- a/pkg/test/ls_test.go +++ b/pkg/test/ls_test.go @@ -18,10 +18,15 @@ package test_test import ( "context" - "os" + "io" "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" + mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/pod" @@ -37,18 +42,25 @@ import ( ) func TestPod_ListPods(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(os.Stdout, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) accountInfo := acc.GetUserAccountInfo() - fd := feed.New(accountInfo, mockClient, logger) + fd := feed.New(accountInfo, mockClient, -1, 0, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() sm := mock2.NewMockSubscriptionManager() - pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, logger) + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) diff --git a/pkg/test/max_file_test.go b/pkg/test/max_file_test.go new file mode 100644 index 00000000..fadf883b --- /dev/null +++ b/pkg/test/max_file_test.go @@ -0,0 +1,90 @@ +package test_test + +import ( + "context" + "io" + "testing" + "time" + + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + + mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" + "github.com/sirupsen/logrus" + + "github.com/plexsysio/taskmanager" + + "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/feed" + "github.com/fairdatasociety/fairOS-dfs/pkg/logging" + "github.com/fairdatasociety/fairOS-dfs/pkg/pod" + "github.com/fairdatasociety/fairOS-dfs/pkg/utils" +) + +func TestMaxFiles(t *testing.T) { + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) + acc := account.New(logger) + _, _, err := acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + tm := taskmanager.New(1, 10, time.Second*15, logger) + defer func() { + _ = tm.Stop(context.Background()) + }() + sm := mock3.NewMockSubscriptionManager() + _, _, err = acc.CreateUserAccount("") + if err != nil { + t.Fatal(err) + } + + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) + podPassword, _ := utils.GetRandString(pod.PasswordLength) + podName, _ := utils.GetRandString(10) + info, err := pod1.CreatePod(podName, "", podPassword) + if err != nil { + t.Fatal(err) + } + + t.Run("create-max-files", func(t *testing.T) { + maxfiles := 100 + filePath := "/" + for i := 1; i <= maxfiles; i++ { + fileName, _ := utils.GetRandString(100) + compression := "" + fileSize := int64(1000) + blockSize := file.MinBlockSize + _, err = uploadFile(t, info.GetFile(), filePath, fileName, compression, podPassword, fileSize, blockSize) + if err != nil { + t.Fatal(err) + } + err = info.GetDirectory().AddEntryToDir("/", podPassword, fileName, true) + if err != nil { + t.Fatal(i, err) + } + } + + // check if the files are present + dirInode, err := info.GetDirectory().GetInode(podPassword, filePath) + if err != nil { + t.Fatal(err) + } + if len(dirInode.FileOrDirNames) != maxfiles { + t.Fatal("files not present") + } + }) +} diff --git a/pkg/test/max_pod_test.go b/pkg/test/max_pod_test.go index 4d568e55..f2a1379a 100644 --- a/pkg/test/max_pod_test.go +++ b/pkg/test/max_pod_test.go @@ -2,16 +2,22 @@ package test_test import ( "context" - "errors" "io" "testing" "time" + "github.com/stretchr/testify/require" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" + "github.com/sirupsen/logrus" "github.com/plexsysio/taskmanager" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" @@ -20,27 +26,32 @@ import ( ) func TestMaxPods(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, 500, time.Second*10, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() sm := mock3.NewMockSubscriptionManager() - pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, logger) + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) t.Run("create-max-pods", func(t *testing.T) { - // t.SkipNow() - - maxPodId := 30 - for i := 1; i <= maxPodId; i++ { + maxPodId := 100 + for i := 0; i < maxPodId; i++ { name, err := utils.GetRandString(utils.MaxPodNameLength) if err != nil { t.Fatal(err) @@ -51,14 +62,11 @@ func TestMaxPods(t *testing.T) { t.Fatalf("error creating pod %s with index %d: %s", name, i, err) } } - name, err := utils.GetRandString(utils.MaxPodNameLength) + + ownPods, _, err := pod1.ListPods() if err != nil { t.Fatal(err) } - podPassword, _ := utils.GetRandString(pod.PasswordLength) - _, err = pod1.CreatePod(name, "", podPassword) - if !errors.Is(err, pod.ErrMaximumPodLimit) { - t.Fatalf("maximum pod limit should have been reached") - } + require.Equal(t, maxPodId, len(ownPods)) }) } diff --git a/pkg/test/new_test.go b/pkg/test/new_test.go index 31606f7a..d779e84b 100644 --- a/pkg/test/new_test.go +++ b/pkg/test/new_test.go @@ -23,10 +23,15 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" + "github.com/sirupsen/logrus" "github.com/plexsysio/taskmanager" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/ensm/eth/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" @@ -34,8 +39,15 @@ import ( ) func TestNew(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) @@ -46,7 +58,7 @@ func TestNew(t *testing.T) { ens := mock2.NewMockNamespaceManager() // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) _, err := userObject.CreateNewUserV2("", "password1", "", "", tm, sm) if !errors.Is(err, user.ErrBlankUsername) { t.Fatal(err) @@ -57,7 +69,7 @@ func TestNew(t *testing.T) { ens := mock2.NewMockNamespaceManager() // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) _, err := userObject.CreateNewUserV2("user1", "password1", "", "", tm, sm) if err != nil && !errors.Is(err, user.ErrPasswordTooSmall) { t.Fatal(err) @@ -100,7 +112,7 @@ func TestNew(t *testing.T) { ens := mock2.NewMockNamespaceManager() user1 := "multicredtester" // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) pass := "password1password1" sr, err := userObject.CreateNewUserV2(user1, pass, "", "", tm, sm) if err != nil { diff --git a/pkg/test/open_test.go b/pkg/test/open_test.go index 4e8dec28..8e1ab00c 100644 --- a/pkg/test/open_test.go +++ b/pkg/test/open_test.go @@ -25,6 +25,11 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" + mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" @@ -40,8 +45,15 @@ import ( ) func TestOpen(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -53,8 +65,8 @@ func TestOpen(t *testing.T) { }() sm := mock2.NewMockSubscriptionManager() - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) - pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) podName1 := "test1" podName2 := "test2" @@ -80,7 +92,8 @@ func TestOpen(t *testing.T) { // create some dir and files addFilesAndDirectories(t, info, pod1, podName1, podPassword) - + fd.CommitFeeds() + <-time.After(time.Second) // open the pod podInfo, err := pod1.OpenPod(podName1) if err != nil { diff --git a/pkg/test/pod_new_test.go b/pkg/test/pod_new_test.go index 8fd7d3f4..1f8a417d 100644 --- a/pkg/test/pod_new_test.go +++ b/pkg/test/pod_new_test.go @@ -19,11 +19,16 @@ package test_test import ( "context" "errors" - "os" + "io" "strings" "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" + mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" "github.com/plexsysio/taskmanager" @@ -38,21 +43,28 @@ import ( ) func TestPodNew(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(os.Stdout, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() sm := mock2.NewMockSubscriptionManager() - pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, logger) + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) podName1 := "test1" podName2 := "test2" diff --git a/pkg/test/pod_sharing_test.go b/pkg/test/pod_sharing_test.go index 41906680..373d841b 100644 --- a/pkg/test/pod_sharing_test.go +++ b/pkg/test/pod_sharing_test.go @@ -23,13 +23,18 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + "github.com/sirupsen/logrus" mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" "github.com/plexsysio/taskmanager" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" @@ -38,21 +43,28 @@ import ( ) func TestShare(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() sm := mock2.NewMockSubscriptionManager() - pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, logger) + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) podName1 := "test1" acc2 := account.New(logger) @@ -60,8 +72,8 @@ func TestShare(t *testing.T) { if err != nil { t.Fatal(err) } - fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, logger) - pod2 := pod.NewPod(mockClient, fd2, acc2, tm, sm, logger) + fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod2 := pod.NewPod(mockClient, fd2, acc2, tm, sm, -1, 0, logger) podName2 := "test2" acc3 := account.New(logger) @@ -69,8 +81,8 @@ func TestShare(t *testing.T) { if err != nil { t.Fatal(err) } - fd3 := feed.New(acc3.GetUserAccountInfo(), mockClient, logger) - pod3 := pod.NewPod(mockClient, fd3, acc3, tm, sm, logger) + fd3 := feed.New(acc3.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod3 := pod.NewPod(mockClient, fd3, acc3, tm, sm, -1, 0, logger) podName3 := "test3" acc4 := account.New(logger) @@ -78,8 +90,8 @@ func TestShare(t *testing.T) { if err != nil { t.Fatal(err) } - fd4 := feed.New(acc4.GetUserAccountInfo(), mockClient, logger) - pod4 := pod.NewPod(mockClient, fd4, acc4, tm, sm, logger) + fd4 := feed.New(acc4.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod4 := pod.NewPod(mockClient, fd4, acc4, tm, sm, -1, 0, logger) podName4 := "test4" acc5 := account.New(logger) @@ -87,8 +99,8 @@ func TestShare(t *testing.T) { if err != nil { t.Fatal(err) } - fd5 := feed.New(acc5.GetUserAccountInfo(), mockClient, logger) - pod5 := pod.NewPod(mockClient, fd5, acc5, tm, sm, logger) + fd5 := feed.New(acc5.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod5 := pod.NewPod(mockClient, fd5, acc5, tm, sm, -1, 0, logger) podName5 := "test5" acc6 := account.New(logger) @@ -96,8 +108,8 @@ func TestShare(t *testing.T) { if err != nil { t.Fatal(err) } - fd6 := feed.New(acc6.GetUserAccountInfo(), mockClient, logger) - pod6 := pod.NewPod(mockClient, fd6, acc6, tm, sm, logger) + fd6 := feed.New(acc6.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod6 := pod.NewPod(mockClient, fd6, acc6, tm, sm, -1, 0, logger) podName6 := "test6" t.Run("share-pod", func(t *testing.T) { @@ -421,8 +433,8 @@ func TestShare(t *testing.T) { if err != nil { t.Fatal(err) } - fd7 := feed.New(acc7.GetUserAccountInfo(), mockClient, logger) - pod7 := pod.NewPod(mockClient, fd7, acc7, tm, sm, logger) + fd7 := feed.New(acc7.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod7 := pod.NewPod(mockClient, fd7, acc7, tm, sm, -1, 0, logger) podName7 := "test7" acc8 := account.New(logger) @@ -430,8 +442,8 @@ func TestShare(t *testing.T) { if err != nil { t.Fatal(err) } - fd8 := feed.New(acc8.GetUserAccountInfo(), mockClient, logger) - pod8 := pod.NewPod(mockClient, fd8, acc8, tm, sm, logger) + fd8 := feed.New(acc8.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod8 := pod.NewPod(mockClient, fd8, acc8, tm, sm, -1, 0, logger) // create sending pod and receiving pod podPassword, _ := utils.GetRandString(pod.PasswordLength) @@ -455,6 +467,8 @@ func TestShare(t *testing.T) { t.Fatal(err) } + pod7.GetFeed().CommitFeeds() + info.GetFeed().CommitFeeds() // receive pod info ref, err := utils.ParseHexReference(sharingRef) if err != nil { @@ -493,17 +507,20 @@ func TestShare(t *testing.T) { if err != nil { t.Fatal(err) } + + pod7.GetFeed().CommitFeeds() + gotInfo.GetFeed().CommitFeeds() // check shared pod entry gotSharedPodInfo, err := pod8.OpenPod(podName7) if err != nil { t.Fatal(err) } dirObject8 := gotSharedPodInfo.GetDirectory() - dirInode1 := dirObject8.GetInode(podPassword, "/parentDir/subDir1") + dirInode1, _ := dirObject8.GetInode(podPassword, "/parentDir/subDir1") if dirInode1 != nil { t.Fatalf("invalid dir entry") } - dirInode1 = dirObject8.GetInode(podPassword, "/parentDir/newSubDir1") + dirInode1, _ = dirObject8.GetInode(podPassword, "/parentDir/newSubDir1") if dirInode1 == nil { t.Fatalf("invalid dir entry") } @@ -513,7 +530,7 @@ func TestShare(t *testing.T) { if dirInode1.Meta.Name != "newSubDir1" { t.Fatalf("invalid dir entry") } - dirInode2 := dirObject8.GetInode(podPassword, "/parentDir/subDir2") + dirInode2, _ := dirObject8.GetInode(podPassword, "/parentDir/subDir2") if dirInode2 == nil { t.Fatalf("invalid dir entry") } diff --git a/pkg/test/pod_stat_test.go b/pkg/test/pod_stat_test.go index 30450e37..2a361f73 100644 --- a/pkg/test/pod_stat_test.go +++ b/pkg/test/pod_stat_test.go @@ -23,13 +23,18 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" + "github.com/sirupsen/logrus" "github.com/fairdatasociety/fairOS-dfs/pkg/utils" "github.com/plexsysio/taskmanager" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" @@ -37,21 +42,28 @@ import ( ) func TestStat(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) }() sm := mock3.NewMockSubscriptionManager() - pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, logger) + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) podName1 := "test1" t.Run("pod-stat", func(t *testing.T) { diff --git a/pkg/test/stat_test.go b/pkg/test/stat_test.go index aed9b365..e868ed7f 100644 --- a/pkg/test/stat_test.go +++ b/pkg/test/stat_test.go @@ -23,20 +23,32 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" + "github.com/sirupsen/logrus" "github.com/fairdatasociety/fairOS-dfs/pkg/user" "github.com/plexsysio/taskmanager" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/ensm/eth/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" ) func TestUserStat(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) @@ -46,7 +58,7 @@ func TestUserStat(t *testing.T) { t.Run("stat-nonexistent-user", func(t *testing.T) { ens := mock2.NewMockNamespaceManager() // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) ui := &user.Info{} // stat the user _, err := userObject.GetUserStat(ui) @@ -58,7 +70,7 @@ func TestUserStat(t *testing.T) { t.Run("stat-user", func(t *testing.T) { ens := mock2.NewMockNamespaceManager() // create user - userObject := user.NewUsers(mockClient, ens, logger) + userObject := user.NewUsers(mockClient, ens, -1, 0, logger) sr, err := userObject.CreateNewUserV2("user1", "password1twelve", "", "", tm, sm) if err != nil { t.Fatal(err) diff --git a/pkg/test/subscription_test.go b/pkg/test/subscription_test.go index 33c9f16c..3f1e5747 100644 --- a/pkg/test/subscription_test.go +++ b/pkg/test/subscription_test.go @@ -2,10 +2,20 @@ package test_test import ( "context" - "os" + "crypto/sha256" + "encoding/base64" + "fmt" + "io" "testing" "time" + "github.com/ethereum/go-ethereum/crypto" + + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" "github.com/ethereum/go-ethereum/common" @@ -22,15 +32,21 @@ import ( ) func TestSubscription(t *testing.T) { - mockClient := mock.NewMockBeeClient() - - logger := logging.New(os.Stdout, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc1 := account.New(logger) _, _, err := acc1.CreateUserAccount("") if err != nil { t.Fatal(err) } - fd := feed.New(acc1.GetUserAccountInfo(), mockClient, logger) + fd := feed.New(acc1.GetUserAccountInfo(), mockClient, -1, 0, logger) tm := taskmanager.New(1, 10, time.Second*15, logger) defer func() { _ = tm.Stop(context.Background()) @@ -43,7 +59,7 @@ func TestSubscription(t *testing.T) { } sm := mock2.NewMockSubscriptionManager() - pod1 := pod.NewPod(mockClient, fd, acc1, tm, sm, logger) + pod1 := pod.NewPod(mockClient, fd, acc1, tm, sm, -1, 0, logger) randomLongPodName1, err := utils.GetRandString(64) if err != nil { @@ -86,8 +102,8 @@ func TestSubscription(t *testing.T) { t.Fatal(err) } - fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, logger) - pod2 := pod.NewPod(mockClient, fd2, acc2, tm, sm, logger) + fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod2 := pod.NewPod(mockClient, fd2, acc2, tm, sm, -1, 0, logger) a2 := acc2.GetUserAccountInfo().GetAddress() addr2 := common.HexToAddress(a2.Hex()) nameHash2, err := goens.NameHash(addr2.Hex()) @@ -190,3 +206,49 @@ func TestSubscription(t *testing.T) { } } + +// + +func TestEncryption(t *testing.T) { + pvtSrt := "153cd9f51ceee5418270957c584b2c8de11f64df6fa2189087aaa89b7deea66e" + VpvtSrt := "cb1b8338e66bd1a94e5a0e69a869e84ada4ef0e7bc18ddd7c7edcb25bcbd6312" + pvtKey, err := crypto.HexToECDSA(pvtSrt) + if err != nil { + t.Fatal(err) + } + + VpvtKey, err := crypto.HexToECDSA(VpvtSrt) + if err != nil { + t.Fatal(err) + } + + pubKey := pvtKey.PublicKey + VpubKey := VpvtKey.PublicKey + crypto.PubkeyToAddress(pubKey) + fmt.Println(pubKey) + fmt.Println(crypto.PubkeyToAddress(pubKey).String()) + fmt.Println(crypto.FromECDSAPub(&pubKey)) + + data := "This is a test string" + fmt.Println(VpubKey.Curve) + a, _ := VpubKey.Curve.ScalarMult(VpubKey.X, VpubKey.Y, pvtKey.D.Bytes()) + secret := sha256.Sum256(a.Bytes()) + fmt.Println(base64.URLEncoding.EncodeToString(secret[:])) + encData, err := utils.EncryptBytes(secret[:], []byte(data)) + if err != nil { + t.Fatal(err) + } + + uEnc := base64.URLEncoding.EncodeToString(encData) + fmt.Println(uEnc) + + b, _ := pubKey.Curve.ScalarMult(pubKey.X, pubKey.Y, VpvtKey.D.Bytes()) + secretB := sha256.Sum256(b.Bytes()) + + data2, err := utils.DecryptBytes(secretB[:], encData) + if err != nil { + t.Fatal(err) + } + + fmt.Println(string(data2)) +} diff --git a/pkg/test/sync_test.go b/pkg/test/sync_test.go index ceb493cc..9802d25c 100644 --- a/pkg/test/sync_test.go +++ b/pkg/test/sync_test.go @@ -22,11 +22,16 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/file" + "github.com/sirupsen/logrus" mock2 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" "github.com/fairdatasociety/fairOS-dfs/pkg/logging" @@ -36,8 +41,15 @@ import ( ) func TestSync(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc := account.New(logger) _, _, err := acc.CreateUserAccount("") if err != nil { @@ -49,8 +61,8 @@ func TestSync(t *testing.T) { }() sm := mock2.NewMockSubscriptionManager() - fd := feed.New(acc.GetUserAccountInfo(), mockClient, logger) - pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, logger) + fd := feed.New(acc.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod1 := pod.NewPod(mockClient, fd, acc, tm, sm, -1, 0, logger) podName1 := "test1" t.Run("sync-pod", func(t *testing.T) { @@ -82,7 +94,7 @@ func TestSync(t *testing.T) { // validate if the directory and files are synced dirObject := gotInfo.GetDirectory() - dirInode1 := dirObject.GetInode(podPassword, "/parentDir/subDir1") + dirInode1, _ := dirObject.GetInode(podPassword, "/parentDir/subDir1") if dirInode1 == nil { t.Fatalf("invalid dir entry") } @@ -92,7 +104,7 @@ func TestSync(t *testing.T) { if dirInode1.Meta.Name != "subDir1" { t.Fatalf("invalid dir entry") } - dirInode2 := dirObject.GetInode(podPassword, "/parentDir/subDir2") + dirInode2, _ := dirObject.GetInode(podPassword, "/parentDir/subDir2") if dirInode2 == nil { t.Fatalf("invalid dir entry") } diff --git a/pkg/test/user_sharing_test.go b/pkg/test/user_sharing_test.go index c38e34f4..09e7455d 100644 --- a/pkg/test/user_sharing_test.go +++ b/pkg/test/user_sharing_test.go @@ -25,6 +25,11 @@ import ( "testing" "time" + mockpost "github.com/ethersphere/bee/pkg/postage/mock" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore/bee" + "github.com/sirupsen/logrus" + mock3 "github.com/fairdatasociety/fairOS-dfs/pkg/subscriptionManager/rpc/mock" "github.com/fairdatasociety/fairOS-dfs/pkg/account" @@ -40,8 +45,15 @@ import ( ) func TestSharing(t *testing.T) { - mockClient := mock.NewMockBeeClient() - logger := logging.New(io.Discard, 0) + storer := mockstorer.New() + beeUrl := mock.NewTestBeeServer(t, mock.TestServerOptions{ + Storer: storer, + PreventRedirect: true, + Post: mockpost.New(mockpost.WithAcceptAll()), + }) + + logger := logging.New(io.Discard, logrus.DebugLevel) + mockClient := bee.NewBeeClient(beeUrl, mock.BatchOkStr, true, logger) acc1 := account.New(logger) _, _, err := acc1.CreateUserAccount("") @@ -58,8 +70,8 @@ func TestSharing(t *testing.T) { }() sm := mock3.NewMockSubscriptionManager() - fd1 := feed.New(acc1.GetUserAccountInfo(), mockClient, logger) - pod1 := pod.NewPod(mockClient, fd1, acc1, tm, sm, logger) + fd1 := feed.New(acc1.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod1 := pod.NewPod(mockClient, fd1, acc1, tm, sm, -1, 0, logger) podName1 := "test1" acc2 := account.New(logger) @@ -71,14 +83,14 @@ func TestSharing(t *testing.T) { if err != nil { t.Fatal(err) } - fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, logger) - pod2 := pod.NewPod(mockClient, fd2, acc2, tm, sm, logger) + fd2 := feed.New(acc2.GetUserAccountInfo(), mockClient, -1, 0, logger) + pod2 := pod.NewPod(mockClient, fd2, acc2, tm, sm, -1, 0, logger) podName2 := "test2" t.Run("sharing-user", func(t *testing.T) { ens := mock2.NewMockNamespaceManager() // create source user - userObject1 := user.NewUsers(mockClient, ens, logger) + userObject1 := user.NewUsers(mockClient, ens, -1, 0, logger) sr0, err := userObject1.CreateNewUserV2("user1", "password1twelve", "", "", tm, sm) if err != nil { t.Fatal(err) @@ -109,6 +121,9 @@ func TestSharing(t *testing.T) { if err != nil { t.Fatal(err) } + fd1.CommitFeeds() + pod1.GetFeed().CommitFeeds() + info1.GetFeed().CommitFeeds() // share file with another user sharingRefString, err := userObject1.ShareFileWithUser("pod1", podPassword, "/parentDir1/file1", "user2", ui0, pod1, info1.GetPodAddress()) if err != nil { @@ -116,7 +131,7 @@ func TestSharing(t *testing.T) { } // create destination user - userObject2 := user.NewUsers(mockClient, ens, logger) + userObject2 := user.NewUsers(mockClient, ens, -1, 0, logger) sr, err := userObject2.CreateNewUserV2("user2", "password1twelve", "", "", tm, sm) if err != nil { t.Fatal(err) diff --git a/pkg/user/info.go b/pkg/user/info.go index 6e550401..e2db8b35 100644 --- a/pkg/user/info.go +++ b/pkg/user/info.go @@ -35,6 +35,7 @@ type Info struct { file *f.File dir *d.Directory pod *pod.Pod + group *pod.Group openPods map[string]*pod.Info openPodsMu *sync.RWMutex } @@ -54,6 +55,11 @@ func (i *Info) GetPod() *pod.Pod { return i.pod } +// GetGroup returns user group handler +func (i *Info) GetGroup() *pod.Group { + return i.group +} + // GetAccount returns user account info func (i *Info) GetAccount() *account.Account { return i.account diff --git a/pkg/user/lite.go b/pkg/user/lite.go index 2e9c3583..4d8dda0d 100644 --- a/pkg/user/lite.go +++ b/pkg/user/lite.go @@ -3,6 +3,8 @@ package user import ( "sync" + acl2 "github.com/fairdatasociety/fairOS-dfs/pkg/acl/acl" + "github.com/fairdatasociety/fairOS-dfs/pkg/auth" "github.com/ethereum/go-ethereum/common/hexutil" @@ -25,7 +27,7 @@ func (u *Users) LoadLiteUser(userName, _, mnemonic, sessionId string, tm taskman acc := account.New(u.logger) accountInfo := acc.GetUserAccountInfo() - fd := feed.New(accountInfo, u.client, u.logger) + fd := feed.New(accountInfo, u.client, u.feedCacheSize, u.feedCacheTTL, u.logger) // create a new base user account with the mnemonic mnemonic, _, err := acc.CreateUserAccount(mnemonic) if err != nil { // skipcq: TCV-001 @@ -35,7 +37,9 @@ func (u *Users) LoadLiteUser(userName, _, mnemonic, sessionId string, tm taskman // Instantiate pod, dir & file objects file := f.NewFile(userName, u.client, fd, accountInfo.GetAddress(), tm, u.logger) dir := d.NewDirectory(userName, u.client, fd, accountInfo.GetAddress(), file, tm, u.logger) - pod := p.NewPod(u.client, fd, acc, tm, sm, u.logger) + pod := p.NewPod(u.client, fd, acc, tm, sm, u.feedCacheSize, u.feedCacheTTL, u.logger) + acl := acl2.NewACL(u.client, fd, u.logger) + group := p.NewGroup(u.client, fd, acc, acl, u.logger) if sessionId == "" { sessionId = auth.GetUniqueSessionId() } @@ -48,6 +52,7 @@ func (u *Users) LoadLiteUser(userName, _, mnemonic, sessionId string, tm taskman file: file, dir: dir, pod: pod, + group: group, openPods: make(map[string]*p.Info), openPodsMu: &sync.RWMutex{}, } diff --git a/pkg/user/login.go b/pkg/user/login.go index d6342a2e..59584136 100644 --- a/pkg/user/login.go +++ b/pkg/user/login.go @@ -20,12 +20,13 @@ import ( "fmt" "sync" - "github.com/fairdatasociety/fairOS-dfs/pkg/auth/jwt" + acl2 "github.com/fairdatasociety/fairOS-dfs/pkg/acl/acl" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/fairdatasociety/fairOS-dfs/pkg/account" "github.com/fairdatasociety/fairOS-dfs/pkg/auth" + "github.com/fairdatasociety/fairOS-dfs/pkg/auth/jwt" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore" d "github.com/fairdatasociety/fairOS-dfs/pkg/dir" "github.com/fairdatasociety/fairOS-dfs/pkg/feed" @@ -67,7 +68,7 @@ func (u *Users) LoginUserV2(userName, passPhrase string, client blockstore.Clien acc := account.New(u.logger) accountInfo := acc.GetUserAccountInfo() // load encrypted private key - fd := feed.New(accountInfo, client, u.logger) + fd := feed.New(accountInfo, client, u.feedCacheSize, u.feedCacheTTL, u.logger) key, err := u.downloadPortableAccount(utils.Address(address), userName, passPhrase, fd) if err != nil { return nil, ErrInvalidPassword @@ -95,11 +96,14 @@ func (u *Users) LoginUserV2(userName, passPhrase string, client blockstore.Clien // Instantiate pod, dir & file objects file := f.NewFile(userName, client, fd, accountInfo.GetAddress(), tm, u.logger) - pod := p.NewPod(u.client, fd, acc, tm, sm, u.logger) + pod := p.NewPod(u.client, fd, acc, tm, sm, u.feedCacheSize, u.feedCacheTTL, u.logger) + acl := acl2.NewACL(u.client, fd, u.logger) + group := p.NewGroup(u.client, fd, acc, acl, u.logger) dir := d.NewDirectory(userName, client, fd, accountInfo.GetAddress(), file, tm, u.logger) if sessionId == "" { sessionId = auth.GetUniqueSessionId() } + ui := &Info{ name: userName, sessionId: sessionId, @@ -108,10 +112,10 @@ func (u *Users) LoginUserV2(userName, passPhrase string, client blockstore.Clien file: file, dir: dir, pod: pod, + group: group, openPods: make(map[string]*p.Info), openPodsMu: &sync.RWMutex{}, } - // set cookie and add user to map if err = u.addUserAndSessionToMap(ui); err != nil { // skipcq: TCV-001 return nil, err @@ -143,10 +147,10 @@ func (u *Users) Logout(sessionId string) error { return ErrUserNotLoggedIn } - // remove from the user map + ui := u.getUserFromMap(sessionId) u.removeUserFromMap(sessionId) - return nil + return ui.feedApi.Close() } // IsUserLoggedIn checks if the user is logged-in from sessionID @@ -164,6 +168,11 @@ func (u *Users) IsUserNameLoggedIn(userName string) bool { return u.isUserNameInMap(userName) } +// GetUsersLoggedIn returns all the users that are loggedin +func (u *Users) GetUsersLoggedIn() map[string]*Info { + return u.getUserMap() +} + // ConnectWallet connects user with wallet. func (u *Users) ConnectWallet(userName, passPhrase, walletAddressHex, signature string, client blockstore.Client) error { // check if username is available (user created) @@ -185,7 +194,7 @@ func (u *Users) ConnectWallet(userName, passPhrase, walletAddressHex, signature acc := account.New(u.logger) accountInfo := acc.GetUserAccountInfo() // load encrypted private key - fd := feed.New(accountInfo, client, u.logger) + fd := feed.New(accountInfo, client, u.feedCacheSize, u.feedCacheTTL, u.logger) key, err := u.downloadPortableAccount(utils.Address(address), userName, passPhrase, fd) if err != nil { u.logger.Errorf(err.Error()) @@ -218,7 +227,7 @@ func (u *Users) LoginWithWallet(addressHex, signature string, client blockstore. acc := account.New(u.logger) accountInfo := acc.GetUserAccountInfo() // load encrypted private key - fd := feed.New(accountInfo, client, u.logger) + fd := feed.New(accountInfo, client, u.feedCacheSize, u.feedCacheTTL, u.logger) key, err := u.downloadPortableAccount(utils.Address(address), addressHex, signature, fd) if err != nil { u.logger.Errorf(err.Error()) @@ -247,7 +256,9 @@ func (u *Users) LoginWithWallet(addressHex, signature string, client blockstore. // Instantiate pod, dir & file objects file := f.NewFile(addressHex, client, fd, accountInfo.GetAddress(), tm, u.logger) - pod := p.NewPod(u.client, fd, acc, tm, sm, u.logger) + pod := p.NewPod(u.client, fd, acc, tm, sm, u.feedCacheSize, u.feedCacheTTL, u.logger) + acl := acl2.NewACL(u.client, fd, u.logger) + group := p.NewGroup(u.client, fd, acc, acl, u.logger) dir := d.NewDirectory(addressHex, client, fd, accountInfo.GetAddress(), file, tm, u.logger) if sessionId == "" { sessionId = auth.GetUniqueSessionId() @@ -260,6 +271,7 @@ func (u *Users) LoginWithWallet(addressHex, signature string, client blockstore. file: file, dir: dir, pod: pod, + group: group, openPods: make(map[string]*p.Info), openPodsMu: &sync.RWMutex{}, } diff --git a/pkg/user/new.go b/pkg/user/new.go index 08ec3610..9da77f2d 100644 --- a/pkg/user/new.go +++ b/pkg/user/new.go @@ -17,9 +17,12 @@ limitations under the License. package user import ( + "errors" "regexp" "sync" + acl2 "github.com/fairdatasociety/fairOS-dfs/pkg/acl/acl" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/fairdatasociety/fairOS-dfs/pkg/account" @@ -78,7 +81,7 @@ func (u *Users) CreateNewUserV2(userName, passPhrase, mnemonic, sessionId string acc := account.New(u.logger) accountInfo := acc.GetUserAccountInfo() - fd := feed.New(accountInfo, u.client, u.logger) + fd := feed.New(accountInfo, u.client, u.feedCacheSize, u.feedCacheTTL, u.logger) // create a new base user account with the mnemonic mnemonic, seed, err := acc.CreateUserAccount(mnemonic) @@ -101,7 +104,7 @@ func (u *Users) CreateNewUserV2(userName, passPhrase, mnemonic, sessionId string nameHash, err = u.createENS(userName, accountInfo) if err != nil { // skipcq: TCV-001 u.logger.Errorf("user: create: create ens failed for user %s: %v", userName, err) - if err == eth.ErrInsufficientBalance { // skipcq: TCV-001 + if errors.Is(err, eth.ErrInsufficientBalance) { // skipcq: TCV-001 return signUp, err } return nil, err // skipcq: TCV-001 @@ -124,7 +127,9 @@ func (u *Users) CreateNewUserV2(userName, passPhrase, mnemonic, sessionId string // Instantiate pod, dir & file objects file := f.NewFile(userName, u.client, fd, accountInfo.GetAddress(), tm, u.logger) dir := d.NewDirectory(userName, u.client, fd, accountInfo.GetAddress(), file, tm, u.logger) - pod := p.NewPod(u.client, fd, acc, tm, sm, u.logger) + pod := p.NewPod(u.client, fd, acc, tm, sm, u.feedCacheSize, u.feedCacheTTL, u.logger) + acl := acl2.NewACL(u.client, fd, u.logger) + group := p.NewGroup(u.client, fd, acc, acl, u.logger) if sessionId == "" { sessionId = auth.GetUniqueSessionId() } @@ -137,6 +142,7 @@ func (u *Users) CreateNewUserV2(userName, passPhrase, mnemonic, sessionId string file: file, dir: dir, pod: pod, + group: group, openPods: make(map[string]*p.Info), openPodsMu: &sync.RWMutex{}, } diff --git a/pkg/user/users.go b/pkg/user/users.go index c67fcb71..98b4d1f0 100644 --- a/pkg/user/users.go +++ b/pkg/user/users.go @@ -18,6 +18,7 @@ package user import ( "sync" + "time" "github.com/fairdatasociety/fairOS-dfs/pkg/blockstore" "github.com/fairdatasociety/fairOS-dfs/pkg/ensm" @@ -31,17 +32,22 @@ type Users struct { userMu *sync.RWMutex logger logging.Logger ens ensm.ENSManager + + feedCacheSize int + feedCacheTTL time.Duration } // NewUsers creates the main user object which stores all the logged-in users and there respective // other data structures. -func NewUsers(client blockstore.Client, ens ensm.ENSManager, logger logging.Logger) *Users { +func NewUsers(client blockstore.Client, ens ensm.ENSManager, feedCacheSize int, feedCacheTTL time.Duration, logger logging.Logger) *Users { return &Users{ - client: client, - userMap: make(map[string]*Info), - userMu: &sync.RWMutex{}, - logger: logger, - ens: ens, + client: client, + userMap: make(map[string]*Info), + userMu: &sync.RWMutex{}, + logger: logger, + ens: ens, + feedCacheSize: feedCacheSize, + feedCacheTTL: feedCacheTTL, } } @@ -72,6 +78,12 @@ func (u *Users) isUserPresentInMap(sessionId string) bool { return false } +func (u *Users) getUserMap() map[string]*Info { + u.userMu.Lock() + defer u.userMu.Unlock() + return u.userMap +} + func (u *Users) isUserNameInMap(userName string) bool { u.userMu.Lock() defer u.userMu.Unlock() diff --git a/swagger/docs.go b/swagger/docs.go index a60201dd..4777addc 100644 --- a/swagger/docs.go +++ b/swagger/docs.go @@ -2017,6 +2017,640 @@ const docTemplate = `{ } } }, + "/v1/group/accept": { + "post": { + "description": "GroupAcceptInviteHandler is the api handler to accept a group invite", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Accept group membersion", + "operationId": "group-accept-invite-handler", + "parameters": [ + { + "description": "reference of the invite", + "name": "reference", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupInviteRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/add": { + "post": { + "description": "GroupAddMemberHandler is the api handler to add a member to a group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Add member to group", + "operationId": "group-add-member-handler", + "parameters": [ + { + "description": "group name, member name and permission", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupAddMemberRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.GroupAddMemberResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/close": { + "post": { + "description": "GroupCloseHandler is the api handler to close a group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Close group", + "operationId": "group-close-handler", + "parameters": [ + { + "description": "group name", + "name": "groupRequest", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupNameRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/delete": { + "delete": { + "description": "GroupDeleteHandler is the api handler to delete a new group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Delete group", + "operationId": "group-delete-handler", + "parameters": [ + { + "description": "group name", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupNameRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/delete-shared": { + "delete": { + "description": "GroupDeleteSharedHandler is the api handler to delete a shared group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Delete shared group", + "operationId": "group-delete-shared-handler", + "parameters": [ + { + "description": "group name", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupNameRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/ls": { + "get": { + "description": "List groups", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "List groups", + "operationId": "group_list", + "parameters": [ + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.GroupListResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/members": { + "get": { + "description": "GroupGetMembers is the api handler to get the members of a group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Get group members", + "operationId": "group-get-members", + "parameters": [ + { + "type": "string", + "description": "group name", + "name": "groupName", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.GroupMembersResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/new": { + "post": { + "description": "GroupCreateHandler is the api handler to create a new group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Create group", + "operationId": "group-create-handler", + "parameters": [ + { + "description": "group name", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupNameRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/open": { + "post": { + "description": "GroupOpenHandler is the api handler to open a group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Open group", + "operationId": "group-open", + "parameters": [ + { + "description": "group name", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupNameRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/permission": { + "get": { + "description": "Get the permission of the user in the group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Get the permission of the user in the group", + "parameters": [ + { + "type": "string", + "description": "Group name", + "name": "groupName", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.GroupPermissionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/remove": { + "post": { + "description": "GroupRemoveMemberHandler is the api handler to remove a member from a group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Remove member from group", + "operationId": "group-remove-member-handler", + "parameters": [ + { + "description": "group name and member name", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupRemoveMemberRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/update-permission": { + "post": { + "description": "GroupUpdatePermissionHandler is the api handler to update a group permission", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Update group permission", + "operationId": "group-update-permission-handler", + "parameters": [ + { + "description": "group name, member name and permission", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupAddMemberRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, "/v1/kv/count": { "post": { "description": "KVCountHandler is the api handler to count the number of rows in a key value table", @@ -4216,6 +4850,91 @@ const docTemplate = `{ } } }, + "api.GroupAddMemberRequest": { + "type": "object", + "properties": { + "groupName": { + "type": "string" + }, + "member": { + "type": "string" + }, + "permission": { + "type": "integer" + } + } + }, + "api.GroupAddMemberResponse": { + "type": "object", + "properties": { + "invite": { + "type": "string" + } + } + }, + "api.GroupInviteRequest": { + "type": "object", + "properties": { + "reference": { + "type": "string" + } + } + }, + "api.GroupListResponse": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": { + "$ref": "#/definitions/pod.GroupItem" + } + }, + "sharedGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/pod.GroupItem" + } + } + } + }, + "api.GroupMembersResponse": { + "type": "object", + "properties": { + "members": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "api.GroupNameRequest": { + "type": "object", + "properties": { + "groupName": { + "type": "string" + } + } + }, + "api.GroupPermissionResponse": { + "type": "object", + "properties": { + "permission": { + "type": "integer" + } + } + }, + "api.GroupRemoveMemberRequest": { + "type": "object", + "properties": { + "groupName": { + "type": "string" + }, + "member": { + "type": "string" + } + } + }, "api.KVEntryDeleteRequest": { "type": "object", "properties": { @@ -4709,6 +5428,32 @@ const docTemplate = `{ } } }, + "pod.GroupItem": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "ownerAddress": { + "type": "string" + }, + "ownerPublicKey": { + "type": "array", + "items": { + "type": "integer" + } + }, + "password": { + "type": "string" + }, + "secret": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, "pod.ShareInfo": { "type": "object", "properties": { diff --git a/swagger/swagger.json b/swagger/swagger.json index e34a7894..cfd8e3fe 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -2008,6 +2008,640 @@ } } }, + "/v1/group/accept": { + "post": { + "description": "GroupAcceptInviteHandler is the api handler to accept a group invite", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Accept group membersion", + "operationId": "group-accept-invite-handler", + "parameters": [ + { + "description": "reference of the invite", + "name": "reference", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupInviteRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/add": { + "post": { + "description": "GroupAddMemberHandler is the api handler to add a member to a group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Add member to group", + "operationId": "group-add-member-handler", + "parameters": [ + { + "description": "group name, member name and permission", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupAddMemberRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.GroupAddMemberResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/close": { + "post": { + "description": "GroupCloseHandler is the api handler to close a group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Close group", + "operationId": "group-close-handler", + "parameters": [ + { + "description": "group name", + "name": "groupRequest", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupNameRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/delete": { + "delete": { + "description": "GroupDeleteHandler is the api handler to delete a new group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Delete group", + "operationId": "group-delete-handler", + "parameters": [ + { + "description": "group name", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupNameRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/delete-shared": { + "delete": { + "description": "GroupDeleteSharedHandler is the api handler to delete a shared group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Delete shared group", + "operationId": "group-delete-shared-handler", + "parameters": [ + { + "description": "group name", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupNameRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/ls": { + "get": { + "description": "List groups", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "List groups", + "operationId": "group_list", + "parameters": [ + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.GroupListResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/members": { + "get": { + "description": "GroupGetMembers is the api handler to get the members of a group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Get group members", + "operationId": "group-get-members", + "parameters": [ + { + "type": "string", + "description": "group name", + "name": "groupName", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.GroupMembersResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/new": { + "post": { + "description": "GroupCreateHandler is the api handler to create a new group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Create group", + "operationId": "group-create-handler", + "parameters": [ + { + "description": "group name", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupNameRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/open": { + "post": { + "description": "GroupOpenHandler is the api handler to open a group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Open group", + "operationId": "group-open", + "parameters": [ + { + "description": "group name", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupNameRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/permission": { + "get": { + "description": "Get the permission of the user in the group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Get the permission of the user in the group", + "parameters": [ + { + "type": "string", + "description": "Group name", + "name": "groupName", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.GroupPermissionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/remove": { + "post": { + "description": "GroupRemoveMemberHandler is the api handler to remove a member from a group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Remove member from group", + "operationId": "group-remove-member-handler", + "parameters": [ + { + "description": "group name and member name", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupRemoveMemberRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, + "/v1/group/update-permission": { + "post": { + "description": "GroupUpdatePermissionHandler is the api handler to update a group permission", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "group" + ], + "summary": "Update group permission", + "operationId": "group-update-permission-handler", + "parameters": [ + { + "description": "group name, member name and permission", + "name": "group_request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.GroupAddMemberRequest" + } + }, + { + "type": "string", + "description": "cookie parameter", + "name": "Cookie", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.response" + } + } + } + } + }, "/v1/kv/count": { "post": { "description": "KVCountHandler is the api handler to count the number of rows in a key value table", @@ -4207,6 +4841,91 @@ } } }, + "api.GroupAddMemberRequest": { + "type": "object", + "properties": { + "groupName": { + "type": "string" + }, + "member": { + "type": "string" + }, + "permission": { + "type": "integer" + } + } + }, + "api.GroupAddMemberResponse": { + "type": "object", + "properties": { + "invite": { + "type": "string" + } + } + }, + "api.GroupInviteRequest": { + "type": "object", + "properties": { + "reference": { + "type": "string" + } + } + }, + "api.GroupListResponse": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": { + "$ref": "#/definitions/pod.GroupItem" + } + }, + "sharedGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/pod.GroupItem" + } + } + } + }, + "api.GroupMembersResponse": { + "type": "object", + "properties": { + "members": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "api.GroupNameRequest": { + "type": "object", + "properties": { + "groupName": { + "type": "string" + } + } + }, + "api.GroupPermissionResponse": { + "type": "object", + "properties": { + "permission": { + "type": "integer" + } + } + }, + "api.GroupRemoveMemberRequest": { + "type": "object", + "properties": { + "groupName": { + "type": "string" + }, + "member": { + "type": "string" + } + } + }, "api.KVEntryDeleteRequest": { "type": "object", "properties": { @@ -4700,6 +5419,32 @@ } } }, + "pod.GroupItem": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "ownerAddress": { + "type": "string" + }, + "ownerPublicKey": { + "type": "array", + "items": { + "type": "integer" + } + }, + "password": { + "type": "string" + }, + "secret": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, "pod.ShareInfo": { "type": "object", "properties": { diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index aa085478..49e38142 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -122,6 +122,60 @@ definitions: fileSharingReference: type: string type: object + api.GroupAddMemberRequest: + properties: + groupName: + type: string + member: + type: string + permission: + type: integer + type: object + api.GroupAddMemberResponse: + properties: + invite: + type: string + type: object + api.GroupInviteRequest: + properties: + reference: + type: string + type: object + api.GroupListResponse: + properties: + groups: + items: + $ref: '#/definitions/pod.GroupItem' + type: array + sharedGroups: + items: + $ref: '#/definitions/pod.GroupItem' + type: array + type: object + api.GroupMembersResponse: + properties: + members: + additionalProperties: + type: integer + type: object + type: object + api.GroupNameRequest: + properties: + groupName: + type: string + type: object + api.GroupPermissionResponse: + properties: + permission: + type: integer + type: object + api.GroupRemoveMemberRequest: + properties: + groupName: + type: string + member: + type: string + type: object api.KVEntryDeleteRequest: properties: key: @@ -440,6 +494,23 @@ definitions: podName: type: string type: object + pod.GroupItem: + properties: + name: + type: string + ownerAddress: + type: string + ownerPublicKey: + items: + type: integer + type: array + password: + type: string + secret: + items: + type: integer + type: array + type: object pod.ShareInfo: properties: password: @@ -1866,6 +1937,432 @@ paths: summary: Upload a file tags: - file + /v1/group/accept: + post: + consumes: + - application/json + description: GroupAcceptInviteHandler is the api handler to accept a group invite + operationId: group-accept-invite-handler + parameters: + - description: reference of the invite + in: body + name: reference + required: true + schema: + $ref: '#/definitions/api.GroupInviteRequest' + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Accept group membersion + tags: + - group + /v1/group/add: + post: + consumes: + - application/json + description: GroupAddMemberHandler is the api handler to add a member to a group + operationId: group-add-member-handler + parameters: + - description: group name, member name and permission + in: body + name: group_request + required: true + schema: + $ref: '#/definitions/api.GroupAddMemberRequest' + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.GroupAddMemberResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Add member to group + tags: + - group + /v1/group/close: + post: + consumes: + - application/json + description: GroupCloseHandler is the api handler to close a group + operationId: group-close-handler + parameters: + - description: group name + in: body + name: groupRequest + required: true + schema: + $ref: '#/definitions/api.GroupNameRequest' + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Close group + tags: + - group + /v1/group/delete: + delete: + consumes: + - application/json + description: GroupDeleteHandler is the api handler to delete a new group + operationId: group-delete-handler + parameters: + - description: group name + in: body + name: group_request + required: true + schema: + $ref: '#/definitions/api.GroupNameRequest' + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Delete group + tags: + - group + /v1/group/delete-shared: + delete: + consumes: + - application/json + description: GroupDeleteSharedHandler is the api handler to delete a shared + group + operationId: group-delete-shared-handler + parameters: + - description: group name + in: body + name: group_request + required: true + schema: + $ref: '#/definitions/api.GroupNameRequest' + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Delete shared group + tags: + - group + /v1/group/ls: + get: + consumes: + - application/json + description: List groups + operationId: group_list + parameters: + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.GroupListResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: List groups + tags: + - group + /v1/group/members: + get: + consumes: + - application/json + description: GroupGetMembers is the api handler to get the members of a group + operationId: group-get-members + parameters: + - description: group name + in: query + name: groupName + required: true + type: string + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.GroupMembersResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Get group members + tags: + - group + /v1/group/new: + post: + consumes: + - application/json + description: GroupCreateHandler is the api handler to create a new group + operationId: group-create-handler + parameters: + - description: group name + in: body + name: group_request + required: true + schema: + $ref: '#/definitions/api.GroupNameRequest' + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/api.response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Create group + tags: + - group + /v1/group/open: + post: + consumes: + - application/json + description: GroupOpenHandler is the api handler to open a group + operationId: group-open + parameters: + - description: group name + in: body + name: group_request + required: true + schema: + $ref: '#/definitions/api.GroupNameRequest' + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Open group + tags: + - group + /v1/group/permission: + get: + consumes: + - application/json + description: Get the permission of the user in the group + parameters: + - description: Group name + in: query + name: groupName + required: true + type: string + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.GroupPermissionResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Get the permission of the user in the group + tags: + - group + /v1/group/remove: + post: + consumes: + - application/json + description: GroupRemoveMemberHandler is the api handler to remove a member + from a group + operationId: group-remove-member-handler + parameters: + - description: group name and member name + in: body + name: group_request + required: true + schema: + $ref: '#/definitions/api.GroupRemoveMemberRequest' + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Remove member from group + tags: + - group + /v1/group/update-permission: + post: + consumes: + - application/json + description: GroupUpdatePermissionHandler is the api handler to update a group + permission + operationId: group-update-permission-handler + parameters: + - description: group name, member name and permission + in: body + name: group_request + required: true + schema: + $ref: '#/definitions/api.GroupAddMemberRequest' + - description: cookie parameter + in: header + name: Cookie + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.response' + summary: Update group permission + tags: + - group /v1/kv/count: post: consumes: diff --git a/wasm/main.go b/wasm/main.go index b23254aa..445865e8 100644 --- a/wasm/main.go +++ b/wasm/main.go @@ -1,4 +1,4 @@ -//go:build js +//go:build wasm package main @@ -6,6 +6,7 @@ import ( "bufio" "bytes" "context" + "encoding/base64" "encoding/json" "fmt" "io" @@ -70,6 +71,19 @@ func registerWasmFunctions() { js.Global().Set("encryptSubscription", js.FuncOf(encryptSubscription)) js.Global().Set("openSubscribedPodFromReference", js.FuncOf(openSubscribedPodFromReference)) + js.Global().Set("groupNew", js.FuncOf(groupNew)) + js.Global().Set("groupOpen", js.FuncOf(groupOpen)) + js.Global().Set("groupClose", js.FuncOf(groupClose)) + js.Global().Set("groupDelete", js.FuncOf(groupDelete)) + js.Global().Set("groupDeleteShared", js.FuncOf(groupDeleteShared)) + js.Global().Set("groupList", js.FuncOf(groupList)) + js.Global().Set("groupInvite", js.FuncOf(groupInvite)) + js.Global().Set("groupAccept", js.FuncOf(groupAccept)) + js.Global().Set("groupRemoveMember", js.FuncOf(groupRemoveMember)) + js.Global().Set("groupUpdatePermission", js.FuncOf(groupUpdatePermission)) + js.Global().Set("groupMembers", js.FuncOf(groupMembers)) + js.Global().Set("groupPermission", js.FuncOf(groupPermission)) + js.Global().Set("dirPresent", js.FuncOf(dirPresent)) js.Global().Set("dirMake", js.FuncOf(dirMake)) js.Global().Set("dirRemove", js.FuncOf(dirRemove)) @@ -84,6 +98,18 @@ func registerWasmFunctions() { js.Global().Set("fileUpload", js.FuncOf(fileUpload)) js.Global().Set("fileDownload", js.FuncOf(fileDownload)) + js.Global().Set("groupDirPresent", js.FuncOf(groupDirPresent)) + js.Global().Set("groupDirMake", js.FuncOf(groupDirMake)) + js.Global().Set("groupDirRemove", js.FuncOf(groupDirRemove)) + js.Global().Set("groupDirList", js.FuncOf(groupDirList)) + js.Global().Set("groupDirStat", js.FuncOf(groupDirStat)) + + js.Global().Set("groupFileShare", js.FuncOf(groupFileShare)) + js.Global().Set("groupFileDelete", js.FuncOf(groupFileDelete)) + js.Global().Set("groupFileStat", js.FuncOf(groupFileStat)) + js.Global().Set("groupFileUpload", js.FuncOf(groupFileUpload)) + js.Global().Set("groupFileDownload", js.FuncOf(groupFileDownload)) + js.Global().Set("kvNewStore", js.FuncOf(kvNewStore)) js.Global().Set("kvList", js.FuncOf(kvList)) js.Global().Set("kvOpen", js.FuncOf(kvOpen)) @@ -113,41 +139,51 @@ func connect(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 4 { - reject.Invoke("not enough arguments. \"connect(beeEndpoint, stampId, rpc, network)\"") + if len(funcArgs) != 6 { + reject.Invoke("not enough arguments. \"connect(beeEndpoint, stampId, rpc, network, subRpc, subContractAddress)\"") return nil } beeEndpoint := funcArgs[0].String() stampId := funcArgs[1].String() rpc := funcArgs[2].String() network := funcArgs[3].String() - //subRpc := funcArgs[4].String() - //subContractAddress := funcArgs[5].String() + subRpc := funcArgs[4].String() + subContractAddress := funcArgs[5].String() if network != "testnet" && network != "play" { reject.Invoke("unknown network. \"use play or testnet\"") return nil } var ( - config *contracts.ENSConfig + config *contracts.ENSConfig + subConfig *contracts.SubscriptionConfig ) if network == "play" { - config, _ = contracts.PlayConfig() + config, subConfig = contracts.PlayConfig() } else { - config, _ = contracts.TestnetConfig(contracts.Sepolia) + config, subConfig = contracts.TestnetConfig(contracts.Sepolia) } config.ProviderBackend = rpc + if subRpc != "" { + subConfig.RPC = subRpc + } + if subContractAddress != "" { + subConfig.DataHubAddress = subContractAddress + } logger := logging.New(os.Stdout, logrus.DebugLevel) go func() { var err error + opts := &dfs.Options{ + Stamp: stampId, + BeeApiEndpoint: beeEndpoint, + EnsConfig: config, + SubscriptionConfig: subConfig, + Logger: logger, + } api, err = dfs.NewDfsAPI( ctx, - beeEndpoint, - stampId, - config, - nil, - logger, + opts, ) if err != nil { reject.Invoke(fmt.Sprintf("failed to connect to fairOS: %s", err.Error())) @@ -510,7 +546,7 @@ func podClose(_ js.Value, funcArgs []js.Value) interface{} { reject := args[1] if len(funcArgs) != 2 { - reject.Invoke("not enough arguments. \"podOpen(sessionId, podName)\"") + reject.Invoke("not enough arguments. \"podClose(sessionId, podName)\"") return nil } sessionId := funcArgs[0].String() @@ -760,30 +796,52 @@ func podReceiveInfo(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func dirPresent(_ js.Value, funcArgs []js.Value) interface{} { +func groupNew(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 3 { - reject.Invoke("not enough arguments. \"dirPresent(sessionId, podName, dirPath)\"") + if len(funcArgs) != 2 { + reject.Invoke("not enough arguments. \"groupNew(sessionId, groupName)\"") return nil } sessionId := funcArgs[0].String() - podName := funcArgs[1].String() - dirPath := funcArgs[2].String() + groupName := funcArgs[1].String() go func() { - present, err := api.IsDirPresent(podName, dirPath, sessionId) + _, err := api.CreateGroup(sessionId, groupName) if err != nil { - reject.Invoke(fmt.Sprintf("dirPresent failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("groupNew failed : %s", err.Error())) return } + resolve.Invoke("group created successfully") + }() + return nil + }) - object := js.Global().Get("Object").New() - object.Set("present", present) + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} - resolve.Invoke(object) +func groupOpen(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 2 { + reject.Invoke("not enough arguments. \"groupOpen(sessionId, groupName)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + + go func() { + _, err := api.OpenGroup(sessionId, groupName) + if err != nil { + reject.Invoke(fmt.Sprintf("groupOpen failed : %s", err.Error())) + return + } + resolve.Invoke("group opened successfully") }() return nil }) @@ -792,26 +850,25 @@ func dirPresent(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func dirMake(_ js.Value, funcArgs []js.Value) interface{} { +func groupClose(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 3 { - reject.Invoke("not enough arguments. \"dirMake(sessionId, podName, dirPath)\"") + if len(funcArgs) != 2 { + reject.Invoke("not enough arguments. \"groupClose(sessionId, groupName)\"") return nil } sessionId := funcArgs[0].String() - podName := funcArgs[1].String() - dirPath := funcArgs[2].String() + groupName := funcArgs[1].String() go func() { - err := api.Mkdir(podName, dirPath, sessionId, 0) + err := api.CloseGroup(sessionId, groupName) if err != nil { - reject.Invoke(fmt.Sprintf("dirMake failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("groupClose failed : %s", err.Error())) return } - resolve.Invoke("directory created successfully") + resolve.Invoke("group closed") }() return nil }) @@ -820,26 +877,25 @@ func dirMake(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func dirRemove(_ js.Value, funcArgs []js.Value) interface{} { +func groupDelete(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 3 { - reject.Invoke("not enough arguments. \"dirRemove(sessionId, podName, dirPath)\"") + if len(funcArgs) != 2 { + reject.Invoke("not enough arguments. \"groupDelete(sessionId, groupName)\"") return nil } sessionId := funcArgs[0].String() - podName := funcArgs[1].String() - dirPath := funcArgs[2].String() + groupName := funcArgs[1].String() go func() { - err := api.RmDir(podName, dirPath, sessionId) + err := api.RemoveGroup(sessionId, groupName) if err != nil { - reject.Invoke(fmt.Sprintf("dirRemove failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("groupDelete failed : %s", err.Error())) return } - resolve.Invoke("directory removed successfully") + resolve.Invoke("group deleted") }() return nil }) @@ -848,56 +904,25 @@ func dirRemove(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func dirList(_ js.Value, funcArgs []js.Value) interface{} { +func groupDeleteShared(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 3 { - reject.Invoke("not enough arguments. \"dirList(sessionId, podName, dirPath)\"") + if len(funcArgs) != 2 { + reject.Invoke("not enough arguments. \"groupDeleteShared(sessionId, groupName)\"") return nil } sessionId := funcArgs[0].String() - podName := funcArgs[1].String() - dirPath := funcArgs[2].String() + groupName := funcArgs[1].String() go func() { - dirs, files, err := api.ListDir(podName, dirPath, sessionId) + err := api.RemoveSharedGroup(sessionId, groupName) if err != nil { - reject.Invoke(fmt.Sprintf("dirList failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("groupDelete failed : %s", err.Error())) return } - filesList := js.Global().Get("Array").New(len(files)) - for i, v := range files { - file := js.Global().Get("Object").New() - file.Set("name", v.Name) - file.Set("contentType", v.ContentType) - file.Set("size", v.Size) - file.Set("blockSize", v.BlockSize) - file.Set("creationTime", v.CreationTime) - file.Set("modificationTime", v.ModificationTime) - file.Set("accessTime", v.AccessTime) - file.Set("mode", v.Mode) - filesList.SetIndex(i, file) - } - dirsList := js.Global().Get("Array").New(len(dirs)) - for i, v := range dirs { - dir := js.Global().Get("Object").New() - dir.Set("name", v.Name) - dir.Set("contentType", v.ContentType) - dir.Set("size", v.Size) - dir.Set("mode", v.Mode) - dir.Set("blockSize", v.BlockSize) - dir.Set("creationTime", v.CreationTime) - dir.Set("modificationTime", v.ModificationTime) - dir.Set("accessTime", v.AccessTime) - dirsList.SetIndex(i, dir) - } - object := js.Global().Get("Object").New() - object.Set("files", filesList) - object.Set("dirs", dirsList) - - resolve.Invoke(object) + resolve.Invoke("shared group deleted") }() return nil }) @@ -906,35 +931,37 @@ func dirList(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func dirStat(_ js.Value, funcArgs []js.Value) interface{} { +func groupList(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 3 { - reject.Invoke("not enough arguments. \"dirStat(sessionId, podName, dirPath)\"") + if len(funcArgs) != 1 { + reject.Invoke("not enough arguments. \"groupList(sessionId)\"") return nil } sessionId := funcArgs[0].String() - podName := funcArgs[1].String() - dirPath := funcArgs[2].String() go func() { - stat, err := api.DirectoryStat(podName, dirPath, sessionId) + groups, err := api.ListGroups(sessionId) if err != nil { - reject.Invoke(fmt.Sprintf("dirStat failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("podList failed : %s", err.Error())) return } + object := js.Global().Get("Object").New() - object.Set("podName", stat.PodName) - object.Set("dirPath", stat.DirPath) - object.Set("dirName", stat.DirName) - object.Set("mode", stat.Mode) - object.Set("creationTime", stat.CreationTime) - object.Set("modificationTime", stat.ModificationTime) - object.Set("accessTime", stat.AccessTime) - object.Set("noOfDirectories", stat.NoOfDirectories) - object.Set("noOfFiles", stat.NoOfFiles) + gs := js.Global().Get("Array").New(len(groups.Groups)) + for i, v := range groups.Groups { + gs.SetIndex(i, js.ValueOf(v)) + } + + sgs := js.Global().Get("Array").New(len(groups.SharedGroups)) + for i, v := range groups.SharedGroups { + sgs.SetIndex(i, js.ValueOf(v)) + } + + object.Set("groups", gs) + object.Set("sharedGroups", sgs) resolve.Invoke(object) }() @@ -945,113 +972,85 @@ func dirStat(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func fileDownload(_ js.Value, funcArgs []js.Value) interface{} { +func groupInvite(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 3 { - reject.Invoke("not enough arguments. \"fileDownload(sessionId, podName, filePath)\"") + + if len(funcArgs) != 4 { + reject.Invoke("not enough arguments. \"groupInvite(sessionId, groupName, member, permission)\"") return nil } sessionId := funcArgs[0].String() - podName := funcArgs[1].String() - filePath := funcArgs[2].String() + groupName := funcArgs[1].String() + member := funcArgs[2].String() + permission := funcArgs[3].Int() go func() { - r, _, err := api.DownloadFile(podName, filePath, sessionId) + reference, err := api.AddMember(sessionId, groupName, member, uint8(permission)) if err != nil { - reject.Invoke(fmt.Sprintf("fileDownload failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("groupInvite failed : %s", err.Error())) return } - defer r.Close() + object := js.Global().Get("Object").New() + object.Set("groupInviteReference", reference) - buf := new(bytes.Buffer) - _, err = buf.ReadFrom(r) - if err != nil { - reject.Invoke(fmt.Sprintf("fileDownload failed : %s", err.Error())) - return - } - a := js.Global().Get("Uint8Array").New(buf.Len()) - js.CopyBytesToJS(a, buf.Bytes()) - resolve.Invoke(a) + resolve.Invoke(object) }() return nil }) + promiseConstructor := js.Global().Get("Promise") return promiseConstructor.New(handler) } -func fileUpload(_ js.Value, funcArgs []js.Value) interface{} { +func groupAccept(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 8 { - reject.Invoke("not enough arguments. \"fileUpload(sessionId, podName, dirPath, file, name, size, blockSize, compression)\"") + + if len(funcArgs) != 2 { + reject.Invoke("not enough arguments. \"groupInvite(sessionId, groupInviteReference)\"") return nil } sessionId := funcArgs[0].String() - podName := funcArgs[1].String() - dirPath := funcArgs[2].String() - array := funcArgs[3] - fileName := funcArgs[4].String() - size := funcArgs[5].Int() - blockSize := funcArgs[6].String() - compression := funcArgs[7].String() - if compression != "" { - if compression != "snappy" && compression != "gzip" { - reject.Invoke("invalid compression value") - return nil - } - } - bs, err := humanize.ParseBytes(blockSize) - if err != nil { - reject.Invoke("invalid blockSize value") - return nil - } + groupInviteReference := funcArgs[1].String() go func() { - inBuf := make([]uint8, array.Get("byteLength").Int()) - js.CopyBytesToGo(inBuf, array) - reader := bytes.NewReader(inBuf) - - err := api.UploadFile(podName, fileName, sessionId, int64(size), reader, dirPath, compression, uint32(bs), 0, true) + err := api.AcceptGroupInvite(sessionId, []byte(groupInviteReference)) if err != nil { - reject.Invoke(fmt.Sprintf("fileUpload failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("groupInvite failed : %s", err.Error())) return } - resolve.Invoke("file uploaded") + resolve.Invoke("group invite accepted") }() return nil }) + promiseConstructor := js.Global().Get("Promise") return promiseConstructor.New(handler) } -func fileShare(_ js.Value, funcArgs []js.Value) interface{} { +func groupRemoveMember(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 4 { - reject.Invoke("not enough arguments. \"fileShare(sessionId, podName, dirPath, destinationUser)\"") + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"groupRemoveMember(sessionId, groupName, member)\"") return nil } sessionId := funcArgs[0].String() - podName := funcArgs[1].String() - dirPath := funcArgs[2].String() - destinationUser := funcArgs[3].String() + groupName := funcArgs[1].String() + member := funcArgs[2].String() go func() { - ref, err := api.ShareFile(podName, dirPath, destinationUser, sessionId) + err := api.RemoveMember(groupName, member, sessionId) if err != nil { - reject.Invoke(fmt.Sprintf("fileShare failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("groupRemoveMember failed : %s", err.Error())) return } - - object := js.Global().Get("Object").New() - object.Set("fileSharingReference", ref) - - resolve.Invoke(object) + resolve.Invoke("member removed from group") }() return nil }) @@ -1060,30 +1059,28 @@ func fileShare(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func fileReceive(_ js.Value, funcArgs []js.Value) interface{} { +func groupUpdatePermission(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] if len(funcArgs) != 4 { - reject.Invoke("not enough arguments. \"fileReceive(sessionId, podName, directory, file_sharing_reference)\"") + reject.Invoke("not enough arguments. \"groupUpdatePermission(sessionId, groupName, member, permission)\"") return nil } sessionId := funcArgs[0].String() - podName := funcArgs[1].String() - directory := funcArgs[2].String() - fileSharingReference := funcArgs[3].String() + groupName := funcArgs[1].String() + member := funcArgs[2].String() + permission := funcArgs[3].Int() go func() { - filePath, err := api.ReceiveFile(podName, sessionId, fileSharingReference, directory) + err := api.UpdatePermission(sessionId, groupName, member, uint8(permission)) if err != nil { - reject.Invoke(fmt.Sprintf("fileReceive failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("groupInvite failed : %s", err.Error())) return } - object := js.Global().Get("Object").New() - object.Set("fileName", filePath) - resolve.Invoke(object) + resolve.Invoke("group permission updated successfully") }() return nil }) @@ -1092,34 +1089,28 @@ func fileReceive(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func fileReceiveInfo(_ js.Value, funcArgs []js.Value) interface{} { +func groupMembers(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] if len(funcArgs) != 2 { - reject.Invoke("not enough arguments. \"fileReceiveInfo(sessionId, fileSharingReference)\"") + reject.Invoke("not enough arguments. \"groupMembers(sessionId, groupName)\"") return nil } sessionId := funcArgs[0].String() - fileSharingReference := funcArgs[2].String() + groupName := funcArgs[1].String() go func() { - receiveInfo, err := api.ReceiveInfo(sessionId, fileSharingReference) + members, err := api.GetGroupMembers(sessionId, groupName) if err != nil { - reject.Invoke(fmt.Sprintf("fileReceiveInfo failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("groupMembers failed : %s", err.Error())) return } object := js.Global().Get("Object").New() - object.Set("name", receiveInfo.FileName) - object.Set("size", receiveInfo.Size) - object.Set("blockSize", receiveInfo.BlockSize) - object.Set("numberOfBlocks", receiveInfo.NumberOfBlocks) - object.Set("contentType", receiveInfo.ContentType) - object.Set("compression", receiveInfo.Compression) - object.Set("sourceAddress", receiveInfo.Sender) - object.Set("destAddress", receiveInfo.Receiver) - object.Set("sharedTime", receiveInfo.SharedTime) + for name, perm := range members { + object.Set(name, perm) + } resolve.Invoke(object) }() @@ -1130,26 +1121,28 @@ func fileReceiveInfo(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func fileDelete(_ js.Value, funcArgs []js.Value) interface{} { +func groupPermission(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 3 { - reject.Invoke("not enough arguments. \"fileDelete(sessionId, podName, podFileWithPath)\"") + if len(funcArgs) != 2 { + reject.Invoke("not enough arguments. \"groupPermission(sessionId, groupName)\"") return nil } sessionId := funcArgs[0].String() - podName := funcArgs[1].String() - filePath := funcArgs[2].String() + groupName := funcArgs[1].String() go func() { - err := api.DeleteFile(podName, filePath, sessionId) + perm, err := api.GetPermission(sessionId, groupName) if err != nil { - reject.Invoke(fmt.Sprintf("fileDelete failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("groupMembers failed : %s", err.Error())) return } - resolve.Invoke("file deleted successfully") + object := js.Global().Get("Object").New() + object.Set("permission", perm) + + resolve.Invoke(object) }() return nil }) @@ -1158,37 +1151,28 @@ func fileDelete(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func fileStat(_ js.Value, funcArgs []js.Value) interface{} { +func dirPresent(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] if len(funcArgs) != 3 { - reject.Invoke("not enough arguments. \"fileStat(sessionId, podName, podFileWithPath)\"") + reject.Invoke("not enough arguments. \"dirPresent(sessionId, podName, dirPath)\"") return nil } sessionId := funcArgs[0].String() podName := funcArgs[1].String() - filePath := funcArgs[2].String() + dirPath := funcArgs[2].String() go func() { - stat, err := api.FileStat(podName, filePath, sessionId) + present, err := api.IsDirPresent(podName, dirPath, sessionId, false) if err != nil { - reject.Invoke(fmt.Sprintf("fileStat failed : %s", err.Error())) + reject.Invoke(fmt.Sprintf("dirPresent failed : %s", err.Error())) return } + object := js.Global().Get("Object").New() - object.Set("podName", stat.PodName) - object.Set("mode", stat.Mode) - object.Set("filePath", stat.FilePath) - object.Set("fileName", stat.FileName) - object.Set("fileSize", stat.FileSize) - object.Set("blockSize", stat.BlockSize) - object.Set("compression", stat.Compression) - object.Set("contentType", stat.ContentType) - object.Set("creationTime", stat.CreationTime) - object.Set("modificationTime", stat.ModificationTime) - object.Set("accessTime", stat.AccessTime) + object.Set("present", present) resolve.Invoke(object) }() @@ -1199,32 +1183,808 @@ func fileStat(_ js.Value, funcArgs []js.Value) interface{} { return promiseConstructor.New(handler) } -func kvNewStore(_ js.Value, funcArgs []js.Value) interface{} { +func dirMake(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 4 { - reject.Invoke("not enough arguments. \"kvNewStore(sessionId, podName, tableName, indexType)\"") + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"dirMake(sessionId, podName, dirPath)\"") return nil } sessionId := funcArgs[0].String() podName := funcArgs[1].String() - tableName := funcArgs[2].String() - idxType := funcArgs[3].String() - if idxType == "" { - idxType = "string" - } + dirPath := funcArgs[2].String() - var indexType collection.IndexType - switch idxType { - case "string": - indexType = collection.StringIndex - case "number": - indexType = collection.NumberIndex - case "bytes": - default: - reject.Invoke("invalid indexType. only string and number are allowed") + go func() { + err := api.Mkdir(podName, dirPath, sessionId, 0, false) + if err != nil { + reject.Invoke(fmt.Sprintf("dirMake failed : %s", err.Error())) + return + } + resolve.Invoke("directory created successfully") + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func dirRemove(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"dirRemove(sessionId, podName, dirPath)\"") + return nil + } + sessionId := funcArgs[0].String() + podName := funcArgs[1].String() + dirPath := funcArgs[2].String() + + go func() { + err := api.RmDir(podName, dirPath, sessionId, false) + if err != nil { + reject.Invoke(fmt.Sprintf("dirRemove failed : %s", err.Error())) + return + } + resolve.Invoke("directory removed successfully") + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func dirList(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"dirList(sessionId, podName, dirPath)\"") + return nil + } + sessionId := funcArgs[0].String() + podName := funcArgs[1].String() + dirPath := funcArgs[2].String() + + go func() { + dirs, files, err := api.ListDir(podName, dirPath, sessionId, false) + if err != nil { + reject.Invoke(fmt.Sprintf("dirList failed : %s", err.Error())) + return + } + filesList := js.Global().Get("Array").New(len(files)) + for i, v := range files { + file := js.Global().Get("Object").New() + file.Set("name", v.Name) + file.Set("contentType", v.ContentType) + file.Set("size", v.Size) + file.Set("blockSize", v.BlockSize) + file.Set("creationTime", v.CreationTime) + file.Set("modificationTime", v.ModificationTime) + file.Set("accessTime", v.AccessTime) + file.Set("mode", v.Mode) + filesList.SetIndex(i, file) + } + dirsList := js.Global().Get("Array").New(len(dirs)) + for i, v := range dirs { + dir := js.Global().Get("Object").New() + dir.Set("name", v.Name) + dir.Set("contentType", v.ContentType) + dir.Set("size", v.Size) + dir.Set("mode", v.Mode) + dir.Set("blockSize", v.BlockSize) + dir.Set("creationTime", v.CreationTime) + dir.Set("modificationTime", v.ModificationTime) + dir.Set("accessTime", v.AccessTime) + dirsList.SetIndex(i, dir) + } + object := js.Global().Get("Object").New() + object.Set("files", filesList) + object.Set("dirs", dirsList) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func dirStat(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"dirStat(sessionId, podName, dirPath)\"") + return nil + } + sessionId := funcArgs[0].String() + podName := funcArgs[1].String() + dirPath := funcArgs[2].String() + + go func() { + stat, err := api.DirectoryStat(podName, dirPath, sessionId, false) + if err != nil { + reject.Invoke(fmt.Sprintf("dirStat failed : %s", err.Error())) + return + } + object := js.Global().Get("Object").New() + object.Set("podName", stat.PodName) + object.Set("dirPath", stat.DirPath) + object.Set("dirName", stat.DirName) + object.Set("mode", stat.Mode) + object.Set("creationTime", stat.CreationTime) + object.Set("modificationTime", stat.ModificationTime) + object.Set("accessTime", stat.AccessTime) + object.Set("noOfDirectories", stat.NoOfDirectories) + object.Set("noOfFiles", stat.NoOfFiles) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func fileDownload(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"fileDownload(sessionId, podName, filePath)\"") + return nil + } + sessionId := funcArgs[0].String() + podName := funcArgs[1].String() + filePath := funcArgs[2].String() + + go func() { + r, _, err := api.DownloadFile(podName, filePath, sessionId, false) + if err != nil { + reject.Invoke(fmt.Sprintf("fileDownload failed : %s", err.Error())) + return + } + defer r.Close() + + buf := new(bytes.Buffer) + _, err = buf.ReadFrom(r) + if err != nil { + reject.Invoke(fmt.Sprintf("fileDownload failed : %s", err.Error())) + return + } + a := js.Global().Get("Uint8Array").New(buf.Len()) + js.CopyBytesToJS(a, buf.Bytes()) + resolve.Invoke(a) + }() + return nil + }) + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func fileUpload(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + if len(funcArgs) != 8 { + reject.Invoke("not enough arguments. \"fileUpload(sessionId, podName, dirPath, file, name, size, blockSize, compression)\"") + return nil + } + sessionId := funcArgs[0].String() + podName := funcArgs[1].String() + dirPath := funcArgs[2].String() + array := funcArgs[3] + fileName := funcArgs[4].String() + size := funcArgs[5].Int() + blockSize := funcArgs[6].String() + compression := funcArgs[7].String() + if compression != "" { + if compression != "snappy" && compression != "gzip" { + reject.Invoke("invalid compression value") + return nil + } + } + bs, err := humanize.ParseBytes(blockSize) + if err != nil { + reject.Invoke("invalid blockSize value") + return nil + } + + go func() { + inBuf := make([]uint8, array.Get("byteLength").Int()) + js.CopyBytesToGo(inBuf, array) + reader := bytes.NewReader(inBuf) + + err := api.UploadFile(podName, fileName, sessionId, int64(size), reader, dirPath, compression, uint32(bs), 0, true, false) + if err != nil { + reject.Invoke(fmt.Sprintf("fileUpload failed : %s", err.Error())) + return + } + resolve.Invoke("file uploaded") + }() + return nil + }) + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func fileShare(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 4 { + reject.Invoke("not enough arguments. \"fileShare(sessionId, podName, dirPath, destinationUser)\"") + return nil + } + sessionId := funcArgs[0].String() + podName := funcArgs[1].String() + dirPath := funcArgs[2].String() + destinationUser := funcArgs[3].String() + + go func() { + ref, err := api.ShareFile(podName, dirPath, destinationUser, sessionId, false) + if err != nil { + reject.Invoke(fmt.Sprintf("fileShare failed : %s", err.Error())) + return + } + + object := js.Global().Get("Object").New() + object.Set("fileSharingReference", ref) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func fileReceive(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 4 { + reject.Invoke("not enough arguments. \"fileReceive(sessionId, podName, directory, file_sharing_reference)\"") + return nil + } + sessionId := funcArgs[0].String() + podName := funcArgs[1].String() + directory := funcArgs[2].String() + fileSharingReference := funcArgs[3].String() + + go func() { + filePath, err := api.ReceiveFile(podName, sessionId, fileSharingReference, directory) + if err != nil { + reject.Invoke(fmt.Sprintf("fileReceive failed : %s", err.Error())) + return + } + object := js.Global().Get("Object").New() + object.Set("fileName", filePath) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func fileReceiveInfo(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 2 { + reject.Invoke("not enough arguments. \"fileReceiveInfo(sessionId, fileSharingReference)\"") + return nil + } + sessionId := funcArgs[0].String() + fileSharingReference := funcArgs[2].String() + + go func() { + receiveInfo, err := api.ReceiveInfo(sessionId, fileSharingReference) + if err != nil { + reject.Invoke(fmt.Sprintf("fileReceiveInfo failed : %s", err.Error())) + return + } + object := js.Global().Get("Object").New() + object.Set("name", receiveInfo.FileName) + object.Set("size", receiveInfo.Size) + object.Set("blockSize", receiveInfo.BlockSize) + object.Set("numberOfBlocks", receiveInfo.NumberOfBlocks) + object.Set("contentType", receiveInfo.ContentType) + object.Set("compression", receiveInfo.Compression) + object.Set("sourceAddress", receiveInfo.Sender) + object.Set("destAddress", receiveInfo.Receiver) + object.Set("sharedTime", receiveInfo.SharedTime) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func fileDelete(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"fileDelete(sessionId, podName, podFileWithPath)\"") + return nil + } + sessionId := funcArgs[0].String() + podName := funcArgs[1].String() + filePath := funcArgs[2].String() + + go func() { + err := api.DeleteFile(podName, filePath, sessionId, false) + if err != nil { + reject.Invoke(fmt.Sprintf("fileDelete failed : %s", err.Error())) + return + } + resolve.Invoke("file deleted successfully") + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func fileStat(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"fileStat(sessionId, podName, podFileWithPath)\"") + return nil + } + sessionId := funcArgs[0].String() + podName := funcArgs[1].String() + filePath := funcArgs[2].String() + + go func() { + stat, err := api.FileStat(podName, filePath, sessionId, false) + if err != nil { + reject.Invoke(fmt.Sprintf("fileStat failed : %s", err.Error())) + return + } + object := js.Global().Get("Object").New() + object.Set("podName", stat.PodName) + object.Set("mode", stat.Mode) + object.Set("filePath", stat.FilePath) + object.Set("fileName", stat.FileName) + object.Set("fileSize", stat.FileSize) + object.Set("blockSize", stat.BlockSize) + object.Set("compression", stat.Compression) + object.Set("contentType", stat.ContentType) + object.Set("creationTime", stat.CreationTime) + object.Set("modificationTime", stat.ModificationTime) + object.Set("accessTime", stat.AccessTime) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func groupDirPresent(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"groupDirPresent(sessionId, groupName, dirPath)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + dirPath := funcArgs[2].String() + + go func() { + present, err := api.IsDirPresent(groupName, dirPath, sessionId, true) + if err != nil { + reject.Invoke(fmt.Sprintf("groupDirRemove failed : %s", err.Error())) + return + } + + object := js.Global().Get("Object").New() + object.Set("present", present) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func groupDirMake(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"groupDirMake(sessionId, groupName, dirPath)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + dirPath := funcArgs[2].String() + + go func() { + err := api.Mkdir(groupName, dirPath, sessionId, 0, true) + if err != nil { + reject.Invoke(fmt.Sprintf("groupDirRemove failed : %s", err.Error())) + return + } + resolve.Invoke("directory created successfully") + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func groupDirRemove(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"groupDirRemove(sessionId, groupName, dirPath)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + dirPath := funcArgs[2].String() + + go func() { + err := api.RmDir(groupName, dirPath, sessionId, true) + if err != nil { + reject.Invoke(fmt.Sprintf("groupDirRemove failed : %s", err.Error())) + return + } + resolve.Invoke("directory removed successfully") + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func groupDirList(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"groupDirList(sessionId, groupName, dirPath)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + dirPath := funcArgs[2].String() + + go func() { + dirs, files, err := api.ListDir(groupName, dirPath, sessionId, true) + if err != nil { + reject.Invoke(fmt.Sprintf("groupDirList failed : %s", err.Error())) + return + } + filesList := js.Global().Get("Array").New(len(files)) + for i, v := range files { + file := js.Global().Get("Object").New() + file.Set("name", v.Name) + file.Set("contentType", v.ContentType) + file.Set("size", v.Size) + file.Set("blockSize", v.BlockSize) + file.Set("creationTime", v.CreationTime) + file.Set("modificationTime", v.ModificationTime) + file.Set("accessTime", v.AccessTime) + file.Set("mode", v.Mode) + filesList.SetIndex(i, file) + } + dirsList := js.Global().Get("Array").New(len(dirs)) + for i, v := range dirs { + dir := js.Global().Get("Object").New() + dir.Set("name", v.Name) + dir.Set("contentType", v.ContentType) + dir.Set("size", v.Size) + dir.Set("mode", v.Mode) + dir.Set("blockSize", v.BlockSize) + dir.Set("creationTime", v.CreationTime) + dir.Set("modificationTime", v.ModificationTime) + dir.Set("accessTime", v.AccessTime) + dirsList.SetIndex(i, dir) + } + object := js.Global().Get("Object").New() + object.Set("files", filesList) + object.Set("dirs", dirsList) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func groupDirStat(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"groupDirStat(sessionId, groupName, dirPath)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + dirPath := funcArgs[2].String() + + go func() { + stat, err := api.DirectoryStat(groupName, dirPath, sessionId, true) + if err != nil { + reject.Invoke(fmt.Sprintf("groupDirStat failed : %s", err.Error())) + return + } + object := js.Global().Get("Object").New() + object.Set("podName", stat.PodName) + object.Set("dirPath", stat.DirPath) + object.Set("dirName", stat.DirName) + object.Set("mode", stat.Mode) + object.Set("creationTime", stat.CreationTime) + object.Set("modificationTime", stat.ModificationTime) + object.Set("accessTime", stat.AccessTime) + object.Set("noOfDirectories", stat.NoOfDirectories) + object.Set("noOfFiles", stat.NoOfFiles) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func groupFileDownload(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"groupFileDownload(sessionId, groupName, filePath)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + filePath := funcArgs[2].String() + + go func() { + r, _, err := api.DownloadFile(groupName, filePath, sessionId, true) + if err != nil { + reject.Invoke(fmt.Sprintf("groupFileDownload failed : %s", err.Error())) + return + } + defer r.Close() + + buf := new(bytes.Buffer) + _, err = buf.ReadFrom(r) + if err != nil { + reject.Invoke(fmt.Sprintf("groupFileDownload failed : %s", err.Error())) + return + } + a := js.Global().Get("Uint8Array").New(buf.Len()) + js.CopyBytesToJS(a, buf.Bytes()) + resolve.Invoke(a) + }() + return nil + }) + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func groupFileUpload(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + if len(funcArgs) != 8 { + reject.Invoke("not enough arguments. \"groupFileUpload(sessionId, groupName, dirPath, file, name, size, blockSize, compression)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + dirPath := funcArgs[2].String() + array := funcArgs[3] + fileName := funcArgs[4].String() + size := funcArgs[5].Int() + blockSize := funcArgs[6].String() + compression := funcArgs[7].String() + if compression != "" { + if compression != "snappy" && compression != "gzip" { + reject.Invoke("invalid compression value") + return nil + } + } + bs, err := humanize.ParseBytes(blockSize) + if err != nil { + reject.Invoke("invalid blockSize value") + return nil + } + + go func() { + inBuf := make([]uint8, array.Get("byteLength").Int()) + js.CopyBytesToGo(inBuf, array) + reader := bytes.NewReader(inBuf) + + err := api.UploadFile(groupName, fileName, sessionId, int64(size), reader, dirPath, compression, uint32(bs), 0, true, true) + if err != nil { + reject.Invoke(fmt.Sprintf("groupFileUpload failed : %s", err.Error())) + return + } + resolve.Invoke("file uploaded") + }() + return nil + }) + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func groupFileShare(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 4 { + reject.Invoke("not enough arguments. \"groupFileShare(sessionId, groupName, dirPath, destinationUser)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + dirPath := funcArgs[2].String() + destinationUser := funcArgs[3].String() + + go func() { + ref, err := api.ShareFile(groupName, dirPath, destinationUser, sessionId, true) + if err != nil { + reject.Invoke(fmt.Sprintf("groupFileShare failed : %s", err.Error())) + return + } + + object := js.Global().Get("Object").New() + object.Set("fileSharingReference", ref) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func groupFileDelete(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"groupFileDelete(sessionId, groupName, podFileWithPath)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + filePath := funcArgs[2].String() + + go func() { + err := api.DeleteFile(groupName, filePath, sessionId, true) + if err != nil { + reject.Invoke(fmt.Sprintf("groupFileDelete failed : %s", err.Error())) + return + } + resolve.Invoke("file deleted successfully") + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func groupFileStat(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 3 { + reject.Invoke("not enough arguments. \"groupFileStat(sessionId, groupName, podFileWithPath)\"") + return nil + } + sessionId := funcArgs[0].String() + groupName := funcArgs[1].String() + filePath := funcArgs[2].String() + + go func() { + stat, err := api.FileStat(groupName, filePath, sessionId, true) + if err != nil { + reject.Invoke(fmt.Sprintf("groupFileStat failed : %s", err.Error())) + return + } + object := js.Global().Get("Object").New() + object.Set("podName", stat.PodName) + object.Set("mode", stat.Mode) + object.Set("filePath", stat.FilePath) + object.Set("fileName", stat.FileName) + object.Set("fileSize", stat.FileSize) + object.Set("blockSize", stat.BlockSize) + object.Set("compression", stat.Compression) + object.Set("contentType", stat.ContentType) + object.Set("creationTime", stat.CreationTime) + object.Set("modificationTime", stat.ModificationTime) + object.Set("accessTime", stat.AccessTime) + + resolve.Invoke(object) + }() + return nil + }) + + promiseConstructor := js.Global().Get("Promise") + return promiseConstructor.New(handler) +} + +func kvNewStore(_ js.Value, funcArgs []js.Value) interface{} { + handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { + resolve := args[0] + reject := args[1] + + if len(funcArgs) != 4 { + reject.Invoke("not enough arguments. \"kvNewStore(sessionId, podName, tableName, indexType)\"") + return nil + } + sessionId := funcArgs[0].String() + podName := funcArgs[1].String() + tableName := funcArgs[2].String() + idxType := funcArgs[3].String() + if idxType == "" { + idxType = "string" + } + + var indexType collection.IndexType + switch idxType { + case "string": + indexType = collection.StringIndex + case "number": + indexType = collection.NumberIndex + case "bytes": + default: + reject.Invoke("invalid indexType. only string and number are allowed") return nil } @@ -1261,8 +2021,16 @@ func kvList(_ js.Value, funcArgs []js.Value) interface{} { reject.Invoke(fmt.Sprintf("kvList failed : %s", err.Error())) return } - resp, _ := json.Marshal(collections) - resolve.Invoke(string(resp)) + object := js.Global().Get("Object").New() + list := js.Global().Get("Array").New() + count := 0 + for i, _ := range collections { + list.SetIndex(count, js.ValueOf(i)) + count++ + } + + object.Set("tables", list) + resolve.Invoke(object) }() return nil }) @@ -1346,8 +2114,10 @@ func kvCount(_ js.Value, funcArgs []js.Value) interface{} { reject.Invoke(fmt.Sprintf("kvCount failed : %s", err.Error())) return } - resp, _ := json.Marshal(count) - resolve.Invoke(resp) + object := js.Global().Get("Object").New() + object.Set("count", count.Count) + object.Set("tableName", count.TableName) + resolve.Invoke(object) }() return nil }) @@ -1406,20 +2176,16 @@ func kvEntryGet(_ js.Value, funcArgs []js.Value) interface{} { key := funcArgs[3].String() go func() { - columns, data, err := api.KVGet(sessionId, podName, tableName, key) + _, data, err := api.KVGet(sessionId, podName, tableName, key) if err != nil { reject.Invoke(fmt.Sprintf("kvEntryGet failed : %s", err.Error())) return } - var res KVResponse - if columns != nil { - res.Keys = columns - } else { - res.Keys = []string{key} - } - res.Values = data - resp, _ := json.Marshal(res) - resolve.Invoke(resp) + object := js.Global().Get("Object").New() + object.Set("key", key) + object.Set("value", base64.StdEncoding.EncodeToString(data)) + + resolve.Invoke(object) }() return nil }) @@ -1461,15 +2227,14 @@ func kvLoadCSV(_ js.Value, funcArgs []js.Value) interface{} { handler := js.FuncOf(func(_ js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] - if len(funcArgs) != 5 { - reject.Invoke("not enough arguments. \"kvLoadCSV(sessionId, podName, tableName, memory, file)\"") + if len(funcArgs) != 4 { + reject.Invoke("not enough arguments. \"kvLoadCSV(sessionId, podName, tableName, file)\"") return nil } sessionId := funcArgs[0].String() podName := funcArgs[1].String() tableName := funcArgs[2].String() - memory := funcArgs[3].Bool() - array := funcArgs[4] + array := funcArgs[3] go func() { inBuf := make([]uint8, array.Get("byteLength").Int()) @@ -1503,7 +2268,7 @@ func kvLoadCSV(_ js.Value, funcArgs []js.Value) interface{} { return } - err = batch.Put(collection.CSVHeaderKey, []byte(record), false, memory) + err = batch.Put(collection.CSVHeaderKey, []byte(record), false, false) if err != nil { failureCount++ readHeader = true @@ -1515,7 +2280,7 @@ func kvLoadCSV(_ js.Value, funcArgs []js.Value) interface{} { } key := strings.Split(record, ",")[0] - err = batch.Put(key, []byte(record), false, memory) + err = batch.Put(key, []byte(record), false, false) if err != nil { failureCount++ continue @@ -1583,20 +2348,17 @@ func kvSeekNext(_ js.Value, funcArgs []js.Value) interface{} { tableName := funcArgs[2].String() go func() { - columns, key, data, err := api.KVGetNext(sessionId, podName, tableName) + _, key, data, err := api.KVGetNext(sessionId, podName, tableName) if err != nil { reject.Invoke(fmt.Sprintf("kvSeekNext failed : %s", err.Error())) return } - var res KVResponse - if columns != nil { - res.Keys = columns - } else { - res.Keys = []string{key} - } - res.Values = data - resp, _ := json.Marshal(res) - resolve.Invoke(resp) + + object := js.Global().Get("Object").New() + object.Set("key", key) + object.Set("value", base64.StdEncoding.EncodeToString(data)) + + resolve.Invoke(object) }() return nil })