diff --git a/cloud/cloud.go b/cloud/cloud.go index 3ee36b3..781428b 100644 --- a/cloud/cloud.go +++ b/cloud/cloud.go @@ -39,6 +39,9 @@ type Conf struct { // WebDAV 协议所需配置 WebDAV *ConfWebDAV + // 本地存储服务配置 + Local *ConfLocal + // 以下值非官方存储服务不必传入 Token string // 云端接口鉴权令牌 AvailableSize int64 // 云端存储可用空间字节数 @@ -68,6 +71,17 @@ type ConfWebDAV struct { ConcurrentReqs int // 并发请求数 } +// ConfLocal 用于描述本地存储服务配置信息。 +type ConfLocal struct { + // 服务端点 (本地文件系统目录) + // + // "D:/path/to/repos/directory" // Windows + // "/path/to/repos/directory" // Unix + Endpoint string + Timeout int // 超时时间,单位:秒 + ConcurrentReqs int // 并发请求数 +} + // Cloud 描述了云端存储服务,接入云端存储服务时需要实现该接口。 type Cloud interface { @@ -205,12 +219,12 @@ func (baseCloud *BaseCloud) GetRepos() (repos []*Repo, size int64, err error) { return } -func (baseCloud *BaseCloud) UploadObject(filePath string, overwrite bool) (err error) { +func (baseCloud *BaseCloud) UploadObject(filePath string, overwrite bool) (length int64, err error) { err = ErrUnsupported return } -func (baseCloud *BaseCloud) UploadBytes(filePath string, data []byte, overwrite bool) (err error) { +func (baseCloud *BaseCloud) UploadBytes(filePath string, data []byte, overwrite bool) (length int64, err error) { err = ErrUnsupported return } @@ -220,7 +234,7 @@ func (baseCloud *BaseCloud) DownloadObject(filePath string) (data []byte, err er return } -func (baseCloud *BaseCloud) RemoveObject(key string) (err error) { +func (baseCloud *BaseCloud) RemoveObject(filePath string) (err error) { err = ErrUnsupported return } @@ -235,7 +249,7 @@ func (baseCloud *BaseCloud) GetIndexes(page int) (indexes []*entity.Index, pageC return } -func (baseCloud *BaseCloud) GetRefsFiles() (fileIDs []string, err error) { +func (baseCloud *BaseCloud) GetRefsFiles() (fileIDs []string, refs []*Ref, err error) { err = ErrUnsupported return } diff --git a/cloud/local.go b/cloud/local.go new file mode 100644 index 0000000..907736b --- /dev/null +++ b/cloud/local.go @@ -0,0 +1,426 @@ +// DejaVu - Data snapshot and sync. +// Copyright (c) 2022-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cloud + +import ( + "math" + "os" + "path" + "path/filepath" + "sort" + "strings" + + "github.com/88250/gulu" + "github.com/shirou/gopsutil/v3/disk" + "github.com/siyuan-note/dejavu/entity" + "github.com/siyuan-note/logging" +) + +// Local 描述了本地文件系统服务实现。 +type Local struct { + *BaseCloud +} + +func NewLocal(baseCloud *BaseCloud) (local *Local) { + local = &Local{ + BaseCloud: baseCloud, + } + return +} + +func (local *Local) CreateRepo(name string) (err error) { + repoPath := path.Join(local.Local.Endpoint, name) + err = os.MkdirAll(repoPath, 0755) + return +} + +func (local *Local) RemoveRepo(name string) (err error) { + repoPath := path.Join(local.Local.Endpoint, name) + err = os.RemoveAll(repoPath) + return +} + +func (local *Local) GetRepos() (repos []*Repo, size int64, err error) { + repos, err = local.listRepos() + if err != nil { + return + } + + for _, repo := range repos { + size += repo.Size + } + return +} + +func (local *Local) UploadObject(filePath string, overwrite bool) (length int64, err error) { + absFilePath := filepath.Join(local.Conf.RepoPath, filePath) + data, err := os.ReadFile(absFilePath) + if err != nil { + return + } + + length, err = local.UploadBytes(filePath, data, overwrite) + return +} + +func (local *Local) UploadBytes(filePath string, data []byte, overwrite bool) (length int64, err error) { + key := path.Join(local.getCurrentRepoDirPath(), filePath) + folder := path.Dir(key) + err = os.MkdirAll(folder, 0755) + if err != nil { + return + } + + if !overwrite { // not overwrite the file + _, err = os.Stat(key) + if err != nil { + if os.IsNotExist(err) { // file not exist + // do nothing and continue + err = nil + } else { // other error + logging.LogErrorf("upload object [%s] failed: %s", key, err) + return + } + } + } + + err = os.WriteFile(key, data, 0644) + if err != nil { + logging.LogErrorf("upload object [%s] failed: %s", key, err) + return + } + + length = int64(len(data)) + + //logging.LogInfof("uploaded object [%s]", key) + return +} + +func (local *Local) DownloadObject(filePath string) (data []byte, err error) { + key := path.Join(local.getCurrentRepoDirPath(), filePath) + data, err = os.ReadFile(key) + if err != nil { + if os.IsNotExist(err) { + err = ErrCloudObjectNotFound + } + return + } + + //logging.LogInfof("downloaded object [%s]", key) + return +} + +func (local *Local) RemoveObject(filePath string) (err error) { + key := path.Join(local.getCurrentRepoDirPath(), filePath) + err = os.Remove(key) + if err != nil { + if os.IsNotExist(err) { + err = nil + } else { + logging.LogErrorf("remove object [%s] failed: %s", key, err) + } + return + } + + //logging.LogInfof("removed object [%s]", key) + return +} + +func (local *Local) ListObjects(pathPrefix string) (objects map[string]*entity.ObjectInfo, err error) { + objects = map[string]*entity.ObjectInfo{} + pathPrefix = path.Join(local.getCurrentRepoDirPath(), pathPrefix) + entries, err := os.ReadDir(pathPrefix) + if err != nil { + logging.LogErrorf("list objects [%s] failed: %s", pathPrefix, err) + return + } + + for _, entry := range entries { + entryInfo, infoErr := entry.Info() + if infoErr != nil { + err = infoErr + logging.LogErrorf("get object [%s] info failed: %s", path.Join(pathPrefix, entry.Name()), err) + return + } + + filePath := entry.Name() + objects[filePath] = &entity.ObjectInfo{ + Path: filePath, + Size: entryInfo.Size(), + } + } + + //logging.LogInfof("list objects [%s]", pathPrefix) + return +} + +func (local *Local) GetTags() (tags []*Ref, err error) { + tags, err = local.listRepoRefs("tags") + if err != nil { + return + } + if 1 > len(tags) { + tags = []*Ref{} + } + return +} + +func (local *Local) GetIndexes(page int) (indexes []*entity.Index, pageCount, totalCount int, err error) { + data, err := local.DownloadObject("indexes-v2.json") + if err != nil { + if os.IsNotExist(err) { + err = nil + } + return + } + + data, err = compressDecoder.DecodeAll(data, nil) + if err != nil { + return + } + + indexesJSON := &Indexes{} + if err = gulu.JSON.UnmarshalJSON(data, indexesJSON); err != nil { + return + } + + totalCount = len(indexesJSON.Indexes) + pageCount = int(math.Ceil(float64(totalCount) / float64(pageSize))) + start := (page - 1) * pageSize + end := page * pageSize + if end > totalCount { + end = totalCount + } + + for i := start; i < end; i++ { + index, getErr := local.repoIndex(indexesJSON.Indexes[i].ID) + if getErr != nil { + logging.LogWarnf("get repo index [%s] failed: %s", indexesJSON.Indexes[i], getErr) + continue + } + + index.Files = nil // Optimize the performance of obtaining cloud snapshots https://github.com/siyuan-note/siyuan/issues/8387 + indexes = append(indexes, index) + } + return +} + +func (local *Local) GetRefsFiles() (fileIDs []string, refs []*Ref, err error) { + refs, err = local.listRepoRefs("") + var files []string + for _, ref := range refs { + index, getErr := local.repoIndex(ref.ID) + if getErr != nil { + return + } + if index == nil { + continue + } + + files = append(files, index.Files...) + } + + fileIDs = gulu.Str.RemoveDuplicatedElem(files) + if 1 > len(fileIDs) { + fileIDs = []string{} + } + return +} + +func (local *Local) GetChunks(checkChunkIDs []string) (chunkIDs []string, err error) { + repoObjectsPath := path.Join(local.getCurrentRepoDirPath(), "objects") + var keys []string + for _, chunkID := range checkChunkIDs { + key := path.Join(repoObjectsPath, chunkID[:2], chunkID[2:]) + keys = append(keys, key) + } + + notFound, err := local.getNotFound(keys) + if err != nil { + return + } + + var notFoundChunkIDs []string + for _, key := range notFound { + chunkID := strings.TrimPrefix(key, repoObjectsPath) + chunkID = strings.ReplaceAll(chunkID, "/", "") + notFoundChunkIDs = append(notFoundChunkIDs, chunkID) + } + + chunkIDs = append(chunkIDs, notFoundChunkIDs...) + chunkIDs = gulu.Str.RemoveDuplicatedElem(chunkIDs) + if 1 > len(chunkIDs) { + chunkIDs = []string{} + } + return +} + +// func (local *Local) GetStat() (stat *Stat, err error) + +func (local *Local) GetIndex(id string) (index *entity.Index, err error) { + index, err = local.repoIndex(id) + if err != nil { + logging.LogErrorf("get repo index [%s] failed: %s", id, err) + return + } + if index == nil { + err = ErrCloudObjectNotFound + return + } + return +} + +func (local *Local) GetConcurrentReqs() (ret int) { + ret = local.Local.ConcurrentReqs + if ret < 1 { + ret = 16 + } + if ret > 1024 { + ret = 1024 + } + return +} + +func (local *Local) GetConf() *Conf { + return local.Conf +} + +func (local *Local) GetAvailableSize() int64 { + usage, err := disk.Usage(local.Local.Endpoint) + if err != nil { + return math.MaxInt64 + } + + return int64(usage.Free) +} + +func (local *Local) AddTraffic(*Traffic) { + return +} + +func (local *Local) listRepos() (repos []*Repo, err error) { + entries, err := os.ReadDir(local.Local.Endpoint) + if err != nil { + logging.LogErrorf("list repos [%s] failed: %s", local.Local.Endpoint, err) + return + } + + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + entryInfo, infoErr := entry.Info() + if infoErr != nil { + err = infoErr + logging.LogErrorf("get repo [%s] info failed: %s", path.Join(local.Local.Endpoint, entry.Name()), err) + return + } + repos = append(repos, &Repo{ + Name: entry.Name(), + Size: entryInfo.Size(), + Updated: entryInfo.ModTime().Local().Format("2006-01-02 15:04:05"), + }) + } + sort.Slice(repos, func(i, j int) bool { return repos[i].Name < repos[j].Name }) + return +} + +func (local *Local) listRepoRefs(refPrefix string) (refs []*Ref, err error) { + keyPath := path.Join(local.getCurrentRepoDirPath(), "refs", refPrefix) + entries, err := os.ReadDir(keyPath) + if err != nil { + logging.LogErrorf("list repo refs [%s] failed: %s", keyPath, err) + return + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + entryInfo, infoErr := entry.Info() + if infoErr != nil { + err = infoErr + logging.LogErrorf("get repo ref [%s] info failed: %s", path.Join(local.Local.Endpoint, entry.Name()), err) + return + } + + data, readErr := os.ReadFile(path.Join(keyPath, entry.Name())) + if readErr != nil { + err = readErr + logging.LogErrorf("get repo ref [%s] ID failed: %s", path.Join(local.Local.Endpoint, entry.Name()), err) + return + } + + id := string(data) + ref := &Ref{ + Name: entry.Name(), + ID: id, + Updated: entryInfo.ModTime().Local().Format("2006-01-02 15:04:05"), + } + refs = append(refs, ref) + } + return +} + +func (local *Local) repoIndex(id string) (index *entity.Index, err error) { + indexFilePath := path.Join(local.getCurrentRepoDirPath(), "indexes", id) + + indexFileInfo, err := os.Stat(indexFilePath) + if err != nil { + return + } + if 1 > indexFileInfo.Size() { + return + } + + data, err := os.ReadFile(indexFilePath) + if err != nil { + return + } + + data, err = compressDecoder.DecodeAll(data, nil) + if err != nil { + return + } + + index = &entity.Index{} + err = gulu.JSON.UnmarshalJSON(data, index) + if err != nil { + return + } + + return +} + +func (local *Local) getNotFound(keys []string) (ret []string, err error) { + if 1 > len(keys) { + return + } + for _, key := range keys { + _, statErr := os.Stat(key) + if os.IsNotExist(statErr) { + ret = append(ret, key) + } + } + return +} + +func (local *Local) getCurrentRepoDirPath() string { + return path.Join(local.Local.Endpoint, local.Dir) +} diff --git a/cloud/s3.go b/cloud/s3.go index 5bf45fd..7f3dc4f 100644 --- a/cloud/s3.go +++ b/cloud/s3.go @@ -230,6 +230,7 @@ func (s3 *S3) GetRefsFiles() (fileIDs []string, refs []*Ref, err error) { for _, ref := range refs { index, getErr := s3.repoIndex(ref.ID) if nil != getErr { + err = getErr return } if nil == index { diff --git a/cloud/webdav.go b/cloud/webdav.go index f155cb6..3d08b8a 100644 --- a/cloud/webdav.go +++ b/cloud/webdav.go @@ -69,20 +69,7 @@ func (webdav *WebDAV) UploadObject(filePath string, overwrite bool) (length int6 return } - key := path.Join(webdav.Dir, "siyuan", "repo", filePath) - folder := path.Dir(key) - err = webdav.mkdirAll(folder) - if nil != err { - return - } - - err = webdav.Client.Write(key, data, 0644) - err = webdav.parseErr(err) - if nil != err { - logging.LogErrorf("upload object [%s] failed: %s", key, err) - return - } - //logging.LogInfof("uploaded object [%s]", key) + length, err = webdav.UploadBytes(filePath, data, overwrite) return } @@ -192,6 +179,7 @@ func (webdav *WebDAV) GetRefsFiles() (fileIDs []string, refs []*Ref, err error) for _, ref := range refs { index, getErr := webdav.repoIndex(repoKey, ref.ID) if nil != getErr { + err = getErr return } if nil == index { @@ -271,7 +259,7 @@ func (webdav *WebDAV) ListObjects(pathPrefix string) (ret map[string]*entity.Obj infos, err := webdav.Client.ReadDir(pathPrefix) if nil != err { - logging.LogErrorf("list objects failed: %s", err) + logging.LogErrorf("list objects [%s] failed: %s", pathPrefix, err) return } diff --git a/go.mod b/go.mod index beb2b2e..6320d99 100644 --- a/go.mod +++ b/go.mod @@ -18,12 +18,14 @@ require ( github.com/qiniu/go-sdk/v7 v7.20.2 github.com/restic/chunker v0.4.0 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 + github.com/shirou/gopsutil/v3 v3.24.5 github.com/siyuan-note/encryption v0.0.0-20231219001248-1e028a4d13b4 github.com/siyuan-note/eventbus v0.0.0-20240627125516-396fdb0f0f97 github.com/siyuan-note/filelock v0.0.0-20241212013445-c66518cdacfa github.com/siyuan-note/httpclient v0.0.0-20241220030420-75c76bea8a71 github.com/siyuan-note/logging v0.0.0-20241218085028-6514639a9742 github.com/studio-b12/gowebdav v0.9.0 + github.com/vmihailenco/msgpack/v5 v5.3.5 ) require ( @@ -45,6 +47,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect @@ -53,11 +56,12 @@ require ( github.com/imroc/req/v3 v3.49.0 // indirect github.com/onsi/ginkgo/v2 v2.22.1 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.48.2 // indirect github.com/refraction-networking/utls v1.6.7 // indirect - github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect diff --git a/go.sum b/go.sum index b63bbfe..3054b77 100644 --- a/go.sum +++ b/go.sum @@ -59,6 +59,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= @@ -102,6 +104,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk= github.com/qiniu/go-sdk/v7 v7.20.2 h1:Jd+ZJs79APo0dnRlv3aA/uEP7b44flP+p32Lek/WxlY= github.com/qiniu/go-sdk/v7 v7.20.2/go.mod h1:ZnEP1rOOi7weF+yzM2qZMHI0z1ht+KjVuNAuKTQW3aM= @@ -118,6 +122,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/siyuan-note/encryption v0.0.0-20231219001248-1e028a4d13b4 h1:kJaw5L/evyW6LcB9IQT8PR4ppx8JVqOFP9Ix3rfwSrc= github.com/siyuan-note/encryption v0.0.0-20231219001248-1e028a4d13b4/go.mod h1:UYcCCY+0wh+GmUoDOaO63j1sV5lgy7laLAk1XhEiUis= github.com/siyuan-note/eventbus v0.0.0-20240627125516-396fdb0f0f97 h1:lM5v8BfNtbOL5jYwhCdMYBcYtr06IYBKjjSLAPMKTM8= @@ -139,7 +145,6 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4dHjU= github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= -github.com/vmihailenco/msgpack v4.0.4/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -147,6 +152,8 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -177,7 +184,9 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=