mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-25 15:19:48 -05:00
Compare commits
1 Commits
v3.0.0
...
userPhotoT
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d073551767 |
@@ -1,3 +1,3 @@
|
||||
# The test runner source for UI tests
|
||||
WEB_COMMITID=9d5615635b05b177946649962d61df78c8d9fc07
|
||||
WEB_COMMITID=8c10b33af6ec3a949f95a57f197ee915d666f343
|
||||
WEB_BRANCH=main
|
||||
|
||||
85
CHANGELOG.md
85
CHANGELOG.md
@@ -1,90 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [3.0.0](https://github.com/opencloud-eu/opencloud/releases/tag/v3.0.0) - 2025-06-10
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@AlexAndBear, @ScharfViktor, @VuiMuich, @aduffeck, @butonic, @fschade, @kulmann, @micbar, @prashant-gurung899, @rhafer
|
||||
|
||||
### 💥 Breaking changes
|
||||
|
||||
- do not automatically expand drive root permissions [[#495](https://github.com/opencloud-eu/opencloud/pull/495)]
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- Enhancement: Introduced support for PrivateLink in WebDAV search responses [[#983](https://github.com/opencloud-eu/opencloud/pull/983)]
|
||||
- Add profile photo [[#864](https://github.com/opencloud-eu/opencloud/pull/864)]
|
||||
- feat: hide close button in collabora [[#828](https://github.com/opencloud-eu/opencloud/pull/828)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- graph: Add $filter to only list (and/or count) member permissions [[#996](https://github.com/opencloud-eu/opencloud/pull/996)]
|
||||
- [full-ci] chore: bump web to v3.0.0 [[#1026](https://github.com/opencloud-eu/opencloud/pull/1026)]
|
||||
- [full-ci] chore: bump web to v3.0.0-alpha.1 [[#972](https://github.com/opencloud-eu/opencloud/pull/972)]
|
||||
- feat: add shareType to sharees field on activities api [[#954](https://github.com/opencloud-eu/opencloud/pull/954)]
|
||||
- graph: Add more $select options to ListPermissions endpoint [[#916](https://github.com/opencloud-eu/opencloud/pull/916)]
|
||||
- feat: add webp format [[#869](https://github.com/opencloud-eu/opencloud/pull/869)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- apiTest. count permission in the list permissions endpoint [[#1010](https://github.com/opencloud-eu/opencloud/pull/1010)]
|
||||
- apiTest. select option for root/permissions endpoint [[#942](https://github.com/opencloud-eu/opencloud/pull/942)]
|
||||
- [full-ci] ApiTest. checking private link in report response [[#993](https://github.com/opencloud-eu/opencloud/pull/993)]
|
||||
- [full-ci] Change `eicar_com.zip` virus file and update tests [[#992](https://github.com/opencloud-eu/opencloud/pull/992)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Fix broken urls in README.md of deployment example [[#1023](https://github.com/opencloud-eu/opencloud/pull/1023)]
|
||||
- Make activitylog service scalable [[#941](https://github.com/opencloud-eu/opencloud/pull/941)]
|
||||
- Fix purging revisions from decomposeds3 blobstores [[#958](https://github.com/opencloud-eu/opencloud/pull/958)]
|
||||
- fix(graph-metadata): lazy cs3 metadata storage initialization [[#946](https://github.com/opencloud-eu/opencloud/pull/946)]
|
||||
- always get the user email for admin user [[#898](https://github.com/opencloud-eu/opencloud/pull/898)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Updated boxes in readme [[#970](https://github.com/opencloud-eu/opencloud/pull/970)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- [decomposed] bump-version-v3.0.0 [[#1030](https://github.com/opencloud-eu/opencloud/pull/1030)]
|
||||
- [full-ci] chore:reva bump v.2.33.1 [[#1027](https://github.com/opencloud-eu/opencloud/pull/1027)]
|
||||
- build(deps): bump i18next from 25.1.2 to 25.2.1 in /services/idp [[#1024](https://github.com/opencloud-eu/opencloud/pull/1024)]
|
||||
- build(deps): bump golang.org/x/image from 0.27.0 to 0.28.0 [[#1012](https://github.com/opencloud-eu/opencloud/pull/1012)]
|
||||
- build(deps): bump @types/node from 22.15.29 to 22.15.30 in /services/idp [[#1008](https://github.com/opencloud-eu/opencloud/pull/1008)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.5.0 to 1.5.1 [[#1000](https://github.com/opencloud-eu/opencloud/pull/1000)]
|
||||
- build(deps): bump golang.org/x/sync from 0.14.0 to 0.15.0 [[#1006](https://github.com/opencloud-eu/opencloud/pull/1006)]
|
||||
- build(deps-dev): bump eslint-plugin-react from 7.37.2 to 7.37.5 in /services/idp [[#1004](https://github.com/opencloud-eu/opencloud/pull/1004)]
|
||||
- build(deps-dev): bump postcss-normalize from 13.0.0 to 13.0.1 in /services/idp [[#1003](https://github.com/opencloud-eu/opencloud/pull/1003)]
|
||||
- build(deps): bump @testing-library/react from 11.2.7 to 12.1.5 in /services/idp [[#994](https://github.com/opencloud-eu/opencloud/pull/994)]
|
||||
- build(deps): bump github.com/blevesearch/bleve/v2 from 2.5.1 to 2.5.2 [[#999](https://github.com/opencloud-eu/opencloud/pull/999)]
|
||||
- build(deps): bump @fontsource/roboto from 5.1.0 to 5.2.5 in /services/idp [[#995](https://github.com/opencloud-eu/opencloud/pull/995)]
|
||||
- build(deps): bump google.golang.org/grpc from 1.72.1 to 1.72.2 [[#991](https://github.com/opencloud-eu/opencloud/pull/991)]
|
||||
- build(deps): bump github.com/nats-io/nats.go from 1.42.0 to 1.43.0 [[#990](https://github.com/opencloud-eu/opencloud/pull/990)]
|
||||
- build(deps): bump @types/jest from 29.5.12 to 29.5.14 in /services/idp [[#987](https://github.com/opencloud-eu/opencloud/pull/987)]
|
||||
- build(deps): bump github.com/leonelquinteros/gotext from 1.7.1 to 1.7.2 [[#981](https://github.com/opencloud-eu/opencloud/pull/981)]
|
||||
- build(deps): bump @types/node from 22.15.19 to 22.15.29 in /services/idp [[#980](https://github.com/opencloud-eu/opencloud/pull/980)]
|
||||
- build(deps): bump github.com/opencloud-eu/libre-graph-api-go from 1.0.6 to 1.0.7 [[#982](https://github.com/opencloud-eu/opencloud/pull/982)]
|
||||
- build(deps-dev): bump sass-loader from 16.0.4 to 16.0.5 in /services/idp [[#979](https://github.com/opencloud-eu/opencloud/pull/979)]
|
||||
- build(deps): bump web-vitals from 4.2.4 to 5.0.2 in /services/idp [[#978](https://github.com/opencloud-eu/opencloud/pull/978)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.4.2 to 1.5.0 [[#977](https://github.com/opencloud-eu/opencloud/pull/977)]
|
||||
- build(deps-dev): bump cldr from 7.5.0 to 7.9.0 in /services/idp [[#975](https://github.com/opencloud-eu/opencloud/pull/975)]
|
||||
- build(deps): bump github.com/olekukonko/tablewriter from 1.0.6 to 1.0.7 [[#974](https://github.com/opencloud-eu/opencloud/pull/974)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc from 0.60.0 to 0.61.0 [[#915](https://github.com/opencloud-eu/opencloud/pull/915)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/zpages from 0.60.0 to 0.61.0 [[#938](https://github.com/opencloud-eu/opencloud/pull/938)]
|
||||
- build(deps): bump @testing-library/user-event from 14.5.2 to 14.6.1 in /services/idp [[#939](https://github.com/opencloud-eu/opencloud/pull/939)]
|
||||
- build(deps): bump i18next-browser-languagedetector from 7.2.1 to 8.1.0 in /services/idp [[#937](https://github.com/opencloud-eu/opencloud/pull/937)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from 0.60.0 to 0.61.0 [[#923](https://github.com/opencloud-eu/opencloud/pull/923)]
|
||||
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.11.3 to 2.11.4 [[#914](https://github.com/opencloud-eu/opencloud/pull/914)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc from 1.35.0 to 1.36.0 [[#907](https://github.com/opencloud-eu/opencloud/pull/907)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/trace from 1.35.0 to 1.36.0 [[#906](https://github.com/opencloud-eu/opencloud/pull/906)]
|
||||
- build(deps): bump github.com/blevesearch/bleve/v2 from 2.5.0 to 2.5.1 [[#900](https://github.com/opencloud-eu/opencloud/pull/900)]
|
||||
- build(deps): bump axios from 1.7.7 to 1.8.2 in /services/idp [[#902](https://github.com/opencloud-eu/opencloud/pull/902)]
|
||||
- build(deps): bump github.com/opencloud-eu/libre-graph-api-go from 1.0.5 to 1.0.6 [[#899](https://github.com/opencloud-eu/opencloud/pull/899)]
|
||||
- build(deps): bump @types/node from 20.14.11 to 22.15.19 in /services/idp [[#886](https://github.com/opencloud-eu/opencloud/pull/886)]
|
||||
- build(deps-dev): bump i18next-conv from 14.1.0 to 15.1.1 in /services/idp [[#887](https://github.com/opencloud-eu/opencloud/pull/887)]
|
||||
- build(deps): bump golang.org/x/net from 0.39.0 to 0.40.0 [[#889](https://github.com/opencloud-eu/opencloud/pull/889)]
|
||||
- build(deps): bump github.com/olekukonko/tablewriter from 0.0.5 to 1.0.6 [[#888](https://github.com/opencloud-eu/opencloud/pull/888)]
|
||||
|
||||
## [2.3.0](https://github.com/opencloud-eu/opencloud/releases/tag/v2.3.0) - 2025-05-19
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@@ -8,7 +8,7 @@ This deployment example is documented in two locations for different audiences:
|
||||
|
||||
* In the [Admin Documentation](https://docs.opencloud.eu/docs/admin/intro)\
|
||||
Providing two variants using detailed configuration step by step guides:\
|
||||
[Docker Compose Setup](https://docs.opencloud.eu/docs/admin/getting-started/container/docker-compose) and [Docker Compose Local](https://docs.opencloud.eu/docs/admin/getting-started/container/docker-compose-local).\
|
||||
[Docker Compose Setup](https://docs.opencloud.eu/docs/admin/getting-started/docker/docker-compose) and [Docker Compose Local](https://docs.opencloud.eu/docs/admin/getting-started/docker/docker-compose-local).\
|
||||
Note that these examples use LetsEncrypt certificates and are intended for production use.
|
||||
|
||||
* In the [Developer Documentation](https://docs.opencloud.eu/docs/dev/intro)\
|
||||
|
||||
60
go.mod
60
go.mod
@@ -11,7 +11,7 @@ require (
|
||||
github.com/Nerzal/gocloak/v13 v13.9.0
|
||||
github.com/bbalet/stopwords v1.0.0
|
||||
github.com/beevik/etree v1.5.1
|
||||
github.com/blevesearch/bleve/v2 v2.5.2
|
||||
github.com/blevesearch/bleve/v2 v2.5.1
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/coreos/go-oidc/v3 v3.14.1
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20241105092511-3ad35d174fc1
|
||||
@@ -49,25 +49,25 @@ require (
|
||||
github.com/jinzhu/now v1.1.5
|
||||
github.com/justinas/alice v1.2.0
|
||||
github.com/kovidgoyal/imaging v1.6.4
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/leonelquinteros/gotext v1.7.1
|
||||
github.com/libregraph/idm v0.5.0
|
||||
github.com/libregraph/lico v0.66.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/mna/pigeon v1.3.0
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/nats-io/nats-server/v2 v2.11.4
|
||||
github.com/nats-io/nats.go v1.43.0
|
||||
github.com/nats-io/nats.go v1.42.0
|
||||
github.com/oklog/run v1.1.0
|
||||
github.com/olekukonko/tablewriter v1.0.7
|
||||
github.com/olekukonko/tablewriter v1.0.6
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/open-policy-agent/opa v1.5.1
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450
|
||||
github.com/opencloud-eu/reva/v2 v2.33.1
|
||||
github.com/open-policy-agent/opa v1.4.2
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.6
|
||||
github.com/opencloud-eu/reva/v2 v2.33.1-0.20250520152851-d33c49bb52b9
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/xattr v0.4.11
|
||||
github.com/pkg/xattr v0.4.10
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/r3labs/sse/v2 v2.10.0
|
||||
github.com/riandyrn/otelchi v0.12.1
|
||||
@@ -88,24 +88,24 @@ require (
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0
|
||||
go-micro.dev/v4 v4.11.0
|
||||
go.etcd.io/bbolt v1.4.0
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0
|
||||
go.opentelemetry.io/contrib/zpages v0.61.0
|
||||
go.opentelemetry.io/contrib/zpages v0.60.0
|
||||
go.opentelemetry.io/otel v1.36.0
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0
|
||||
go.opentelemetry.io/otel/sdk v1.36.0
|
||||
go.opentelemetry.io/otel/trace v1.36.0
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
|
||||
golang.org/x/image v0.28.0
|
||||
golang.org/x/image v0.27.0
|
||||
golang.org/x/net v0.40.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.15.0
|
||||
golang.org/x/sync v0.14.0
|
||||
golang.org/x/term v0.32.0
|
||||
golang.org/x/text v0.26.0
|
||||
golang.org/x/text v0.25.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237
|
||||
google.golang.org/grpc v1.72.2
|
||||
google.golang.org/grpc v1.72.1
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gotest.tools/v3 v3.5.2
|
||||
@@ -148,7 +148,7 @@ require (
|
||||
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.3 // indirect
|
||||
github.com/bluele/gcache v0.0.2 // indirect
|
||||
github.com/bombsimon/logrusr/v3 v3.1.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
|
||||
@@ -156,7 +156,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cornelk/hashmap v1.0.8 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
@@ -177,7 +177,7 @@ require (
|
||||
github.com/evanphx/json-patch/v5 v5.5.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/gdexlab/go-render v1.0.1 // indirect
|
||||
github.com/go-acme/lego/v4 v4.4.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
@@ -246,7 +246,7 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.27 // indirect
|
||||
github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b // indirect
|
||||
github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 // indirect
|
||||
github.com/miekg/dns v1.1.57 // indirect
|
||||
@@ -254,7 +254,7 @@ require (
|
||||
github.com/minio/crc64nvme v1.0.1 // indirect
|
||||
github.com/minio/highwayhash v1.0.3 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.92 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.89 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -266,19 +266,18 @@ require (
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect
|
||||
github.com/olekukonko/ll v0.0.8 // indirect
|
||||
github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/pablodz/inotifywaitgo v0.0.9 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/pquerna/cachecontrol v0.2.0 // indirect
|
||||
github.com/prometheus/alertmanager v0.28.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/prometheus/statsd_exporter v0.22.8 // indirect
|
||||
@@ -304,10 +303,8 @@ require (
|
||||
github.com/tchap/go-patricia/v2 v2.3.2 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
||||
github.com/trustelem/zxcvbn v1.0.1 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.26 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/wk8/go-ordered-map v1.0.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
@@ -316,21 +313,22 @@ require (
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
github.com/yashtewari/glob-intersection v0.2.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.0 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.20 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.20 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.20 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
|
||||
122
go.sum
122
go.sum
@@ -109,8 +109,6 @@ github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6Ct
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.976/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 h1:I9YN9WMo3SUh7p/4wKeNvD/IQla3U3SUa61U7ul+xM4=
|
||||
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964/go.mod h1:eFiR01PwTcpbzXtdMces7zxg6utvFM5puiWHpWB8D/k=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0=
|
||||
@@ -134,6 +132,7 @@ github.com/bbalet/stopwords v1.0.0 h1:0TnGycCtY0zZi4ltKoOGRFIlZHv0WqpoIGUsObjztf
|
||||
github.com/bbalet/stopwords v1.0.0/go.mod h1:sAWrQoDMfqARGIn4s6dp7OW7ISrshUD8IP2q3KoqPjc=
|
||||
github.com/beevik/etree v1.5.1 h1:TC3zyxYp+81wAmbsi8SWUpZCurbxa6S8RITYRSkNRwo=
|
||||
github.com/beevik/etree v1.5.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -147,8 +146,8 @@ github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6
|
||||
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
|
||||
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/blevesearch/bleve/v2 v2.5.2 h1:Ab0r0MODV2C5A6BEL87GqLBySqp/s9xFgceCju6BQk8=
|
||||
github.com/blevesearch/bleve/v2 v2.5.2/go.mod h1:5Dj6dUQxZM6aqYT3eutTD/GpWKGFSsV8f7LDidFbwXo=
|
||||
github.com/blevesearch/bleve/v2 v2.5.1 h1:cc/O++W2Hcjp1SU5ETHeE+QYWv2oV88ldYEPowdmg8M=
|
||||
github.com/blevesearch/bleve/v2 v2.5.1/go.mod h1:9g/wnbWKm9AgXrU8Ecqi+IDdqjUHWymwkQRDg+5tafU=
|
||||
github.com/blevesearch/bleve_index_api v1.2.8 h1:Y98Pu5/MdlkRyLM0qDHostYo7i+Vv1cDNhqTeR4Sy6Y=
|
||||
github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/geo v0.2.3 h1:K9/vbGI9ehlXdxjxDRJtoAMt7zGAsMIzc6n8zWcwnhg=
|
||||
@@ -181,8 +180,8 @@ github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT
|
||||
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww=
|
||||
github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs=
|
||||
github.com/blevesearch/zapx/v16 v16.2.3 h1:7Y0r+a3diEvlazsncexq1qoFOcBd64xwMS7aDm4lo1s=
|
||||
github.com/blevesearch/zapx/v16 v16.2.3/go.mod h1:wVJ+GtURAaRG9KQAMNYyklq0egV+XJlGcXNCE0OFjjA=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
@@ -227,9 +226,8 @@ github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
@@ -320,8 +318,8 @@ github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387 h1:Y3wZgTr29sL
|
||||
github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387/go.mod h1:HpntrRsQA6RKNXy2Nbr4kVj+NO3OYWpAQUVxeya+3sU=
|
||||
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.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gdexlab/go-render v1.0.1 h1:rxqB3vo5s4n1kF0ySmoNeSPRYkEsyHgln4jFIQY7v0U=
|
||||
@@ -716,8 +714,8 @@ github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvf
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
|
||||
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
|
||||
github.com/leonelquinteros/gotext v1.7.1 h1:/JNPeE3lY5JeVYv2+KBpz39994W3W9fmZCGq3eO9Ri8=
|
||||
github.com/leonelquinteros/gotext v1.7.1/go.mod h1:I0WoFDn9u2D3VbPnnDPT8mzZu0iSXG8iih+AH2fHHqg=
|
||||
github.com/libregraph/idm v0.5.0 h1:tDMwKbAOZzdeDYMxVlY5PbSqRKO7dbAW9KT42A51WSk=
|
||||
github.com/libregraph/idm v0.5.0/go.mod h1:BGMwIQ/6orJSPVzJ1x6kgG2JyG9GY05YFmbsnaD80k0=
|
||||
github.com/libregraph/lico v0.66.0 h1:7T6fD1YF0Ep9n0g4KN6dvWHTlDC3awrQpgsP5GdYCF4=
|
||||
@@ -763,8 +761,8 @@ github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
|
||||
github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
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=
|
||||
@@ -784,8 +782,8 @@ github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD
|
||||
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.92 h1:jpBFWyRS3p8P/9tsRc+NuvqoFi7qAmTCFPoRFmobbVw=
|
||||
github.com/minio/minio-go/v7 v7.0.92/go.mod h1:vTIc8DNcnAZIhyFsk8EB90AbPjj3j68aWIEQCiPj7d0=
|
||||
github.com/minio/minio-go/v7 v7.0.89 h1:hx4xV5wwTUfyv8LarhJAwNecnXpoTsj9v3f3q/ZkiJU=
|
||||
github.com/minio/minio-go/v7 v7.0.89/go.mod h1:2rFnGAp02p7Dddo1Fq4S2wYOfpF0MUTSeLTRC90I204=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
@@ -824,8 +822,8 @@ github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI=
|
||||
github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
|
||||
github.com/nats-io/nats-server/v2 v2.11.4 h1:oQhvy6He6ER926sGqIKBKuYHH4BGnUQCNb0Y5Qa+M54=
|
||||
github.com/nats-io/nats-server/v2 v2.11.4/go.mod h1:jFnKKwbNeq6IfLHq+OMnl7vrFRihQ/MkhRbiWfjLdjU=
|
||||
github.com/nats-io/nats.go v1.43.0 h1:uRFZ2FEoRvP64+UUhaTokyS18XBCR/xM2vQZKO4i8ug=
|
||||
github.com/nats-io/nats.go v1.43.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nats.go v1.42.0 h1:ynIMupIOvf/ZWH/b2qda6WGKGNSjwOUutTpWRvAmhaM=
|
||||
github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
@@ -846,11 +844,11 @@ github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DV
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo=
|
||||
github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc=
|
||||
github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985 h1:V2wKiwjwAfRJRtUP6pC7wt4opeF14enO0du2dRV6Llo=
|
||||
github.com/olekukonko/ll v0.0.8-0.20250516010636-22ea57d81985/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
|
||||
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||
github.com/olekukonko/tablewriter v1.0.6 h1:/T45mIHc5hcEvibgzBzvMy7ruT+RjgoQRvkHbnl6OWA=
|
||||
github.com/olekukonko/tablewriter v1.0.6/go.mod h1:SJ0MV1aHb/89fLcsBMXMp30Xg3g5eGoOUu0RptEk4AU=
|
||||
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=
|
||||
@@ -863,14 +861,14 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
|
||||
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/open-policy-agent/opa v1.5.1 h1:LTxxBJusMVjfs67W4FoRcnMfXADIGFMzpqnfk6D08Cg=
|
||||
github.com/open-policy-agent/opa v1.5.1/go.mod h1:bYbS7u+uhTI+cxHQIpzvr5hxX0hV7urWtY+38ZtjMgk=
|
||||
github.com/open-policy-agent/opa v1.4.2 h1:ag4upP7zMsa4WE2p1pwAFeG4Pn3mNwfAx9DLhhJfbjU=
|
||||
github.com/open-policy-agent/opa v1.4.2/go.mod h1:DNzZPKqKh4U0n0ANxcCVlw8lCSv2c+h5G/3QvSYdWZ8=
|
||||
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a h1:Sakl76blJAaM6NxylVkgSzktjo2dS504iDotEFJsh3M=
|
||||
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450 h1:QWn9G2f1R/EbyZSbkjtd9jqNq9X0NIphmmD4KYLNZtA=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
|
||||
github.com/opencloud-eu/reva/v2 v2.33.1 h1:dm3AxLx7MCHPI9h23iYr/Uw/AQHmQN+G4XlbZQnm35Y=
|
||||
github.com/opencloud-eu/reva/v2 v2.33.1/go.mod h1:0Eu27TSdmj1+0PGn8MjFNh84nW16kGzt2Cxdz9oUncM=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.6 h1:bUQq0tfaLboZZmPuI6C1rr/wFIVOIM9IsE1WqI5QsDA=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.6/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
|
||||
github.com/opencloud-eu/reva/v2 v2.33.1-0.20250520152851-d33c49bb52b9 h1:7y8gTqVQSXLyAqeUFesbI58OkgGcS5fmfq2f3e95XOI=
|
||||
github.com/opencloud-eu/reva/v2 v2.33.1-0.20250520152851-d33c49bb52b9/go.mod h1:8S3B+GPFdGMcNL/pkSHI4K2/E0ICvR7qxllE7Ooydm8=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
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=
|
||||
@@ -892,8 +890,6 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
@@ -905,8 +901,8 @@ 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.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
|
||||
github.com/pkg/xattr v0.4.11 h1:DA7usy0rTMNMGvm06b5LhZUwiPj708D89S8DkXpMB1E=
|
||||
github.com/pkg/xattr v0.4.11/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA=
|
||||
github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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=
|
||||
@@ -937,8 +933,8 @@ github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:
|
||||
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.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.0.0-20170706130215-fb369f752a7f/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
@@ -1091,8 +1087,6 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
@@ -1113,8 +1107,6 @@ github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/vektah/gqlparser/v2 v2.5.26 h1:REqqFkO8+SOEgZHR/eHScjjVjGS8Nk3RMO/juiTobN4=
|
||||
github.com/vektah/gqlparser/v2 v2.5.26/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
@@ -1156,12 +1148,12 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
|
||||
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
|
||||
go.etcd.io/etcd/api/v3 v3.6.0 h1:vdbkcUBGLf1vfopoGE/uS3Nv0KPyIpUV/HM6w9yx2kM=
|
||||
go.etcd.io/etcd/api/v3 v3.6.0/go.mod h1:Wt5yZqEmxgTNJGHob7mTVBJDZNXiHPtXTcPab37iFOw=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.0 h1:nchnPqpuxvv3UuGGHaz0DQKYi5EIW5wOYsgUNRc365k=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.0/go.mod h1:Jv5SFWMnGvIBn8o3OaBq/PnT0jjsX8iNokAUessNjoA=
|
||||
go.etcd.io/etcd/client/v3 v3.6.0 h1:/yjKzD+HW5v/3DVj9tpwFxzNbu8hjcKID183ug9duWk=
|
||||
go.etcd.io/etcd/client/v3 v3.6.0/go.mod h1:Jzk/Knqe06pkOZPHXsQ0+vNDvMQrgIqJ0W8DwPdMJMg=
|
||||
go.etcd.io/etcd/api/v3 v3.5.20 h1:aKfz3nPZECWoZJXMSH9y6h2adXjtOHaHTGEVCuCmaz0=
|
||||
go.etcd.io/etcd/api/v3 v3.5.20/go.mod h1:QqKGViq4KTgOG43dr/uH0vmGWIaoJY3ggFi6ZH0TH/U=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.20 h1:sZIAtra+xCo56gdf6BR62to/hiie5Bwl7hQIqMzVTEM=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.20/go.mod h1:qaOi1k4ZA9lVLejXNvyPABrVEe7VymMF2433yyRQ7O0=
|
||||
go.etcd.io/etcd/client/v3 v3.5.20 h1:jMT2MwQEhyvhQg49Cec+1ZHJzfUf6ZgcmV0GjPv0tIQ=
|
||||
go.etcd.io/etcd/client/v3 v3.5.20/go.mod h1:J5lbzYRMUR20YolS5UjlqqMcu3/wdEvG5VNBhzyo3m0=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -1174,12 +1166,12 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/contrib/zpages v0.61.0 h1:tYvUj377Dn3k1wf1le/f8YWSNQ8k0byS3jK8PiIXu9Y=
|
||||
go.opentelemetry.io/contrib/zpages v0.61.0/go.mod h1:MFNPHMJOGA1P6m5501ANjOJDp4A9BUQja1Y53CDL8LQ=
|
||||
go.opentelemetry.io/contrib/zpages v0.60.0 h1:wOM9ie1Hz4H88L9KE6GrGbKJhfm+8F1NfW/Y3q9Xt+8=
|
||||
go.opentelemetry.io/contrib/zpages v0.60.0/go.mod h1:xqfToSRGh2MYUsfyErNz8jnNDPlnpZqWM/y6Z2Cx7xw=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||
@@ -1203,6 +1195,8 @@ go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAj
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
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/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
@@ -1215,8 +1209,8 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
|
||||
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -1238,8 +1232,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/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=
|
||||
@@ -1255,8 +1249,8 @@ golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScy
|
||||
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/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
|
||||
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
|
||||
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
||||
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=
|
||||
@@ -1281,8 +1275,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
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/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1365,8 +1359,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1477,8 +1471,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
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=
|
||||
@@ -1541,8 +1535,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
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=
|
||||
@@ -1623,8 +1617,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
|
||||
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.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
|
||||
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e h1:m7aQHHqd0q89mRwhwS9Bx2rjyl/hsFAeta+uGrHsQaU=
|
||||
google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e/go.mod h1:gID3PKrg7pWKntu9Ss6zTLJ0ttC0X9IHgREOCZwbCVU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
|
||||
@@ -19,7 +19,6 @@ var (
|
||||
// 9113a718-8285-4b32-9042-f930f1a58ac2.REV.2024-05-22T07:32:53.89969726Z.mpk
|
||||
// 9113a718-8285-4b32-9042-f930f1a58ac2.REV.2024-05-22T07:32:53.89969726Z.mlock
|
||||
_versionRegex = regexp.MustCompile(`\.REV\.[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+Z*`)
|
||||
_spaceIDRegex = regexp.MustCompile(`/spaces/(.+)/nodes/`)
|
||||
)
|
||||
|
||||
// DelBlobstore is the interface for a blobstore that can delete blobs.
|
||||
@@ -146,10 +145,7 @@ func PurgeRevisions(nodes <-chan string, bs DelBlobstore, dryRun, verbose bool)
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
spaceID, blobID string
|
||||
)
|
||||
|
||||
var blobID string
|
||||
e := filepath.Ext(d)
|
||||
switch e {
|
||||
case ".mpk":
|
||||
@@ -158,12 +154,6 @@ func PurgeRevisions(nodes <-chan string, bs DelBlobstore, dryRun, verbose bool)
|
||||
fmt.Printf("error getting blobID from %s: %v\n", d, err)
|
||||
continue
|
||||
}
|
||||
matches := _spaceIDRegex.FindStringSubmatch(d)
|
||||
if len(matches) != 2 {
|
||||
fmt.Printf("error extracting spaceID from %s\n", d)
|
||||
continue
|
||||
}
|
||||
spaceID = strings.ReplaceAll(matches[1], "/", "")
|
||||
|
||||
countBlobs++
|
||||
case ".mlock":
|
||||
@@ -175,7 +165,7 @@ func PurgeRevisions(nodes <-chan string, bs DelBlobstore, dryRun, verbose bool)
|
||||
if !dryRun {
|
||||
if blobID != "" {
|
||||
// TODO: needs spaceID for decomposeds3
|
||||
if err := bs.Delete(&node.Node{BaseNode: node.BaseNode{SpaceID: spaceID}, BlobID: blobID}); err != nil {
|
||||
if err := bs.Delete(&node.Node{BlobID: blobID}); err != nil {
|
||||
fmt.Printf("error deleting blob %s: %v\n", blobID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/metadata"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/storage"
|
||||
)
|
||||
|
||||
// Lazy is a lazy storage implementation that initializes the underlying storage only when needed.
|
||||
type Lazy struct {
|
||||
next func() (metadata.Storage, error)
|
||||
|
||||
initName string `validate:"required"`
|
||||
initCTX context.Context `validate:"required"`
|
||||
}
|
||||
|
||||
func NewLazyStorage(next metadata.Storage) (*Lazy, error) {
|
||||
s := &Lazy{}
|
||||
s.next = sync.OnceValues[metadata.Storage, error](func() (metadata.Storage, error) {
|
||||
if err := validator.New(validator.WithPrivateFieldValidation()).Struct(s); err != nil {
|
||||
return nil, errors.Join(storage.ErrStorageInitialization, storage.ErrStorageValidation, err)
|
||||
}
|
||||
|
||||
if err := next.Init(s.initCTX, s.initName); err != nil {
|
||||
return nil, errors.Join(storage.ErrStorageInitialization, err)
|
||||
}
|
||||
|
||||
return next, nil
|
||||
})
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Backend wraps the backend of the next storage
|
||||
func (s *Lazy) Backend() string {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return next.Backend()
|
||||
}
|
||||
|
||||
// Init prepares the required data for the underlying lazy storage initialization
|
||||
func (s *Lazy) Init(ctx context.Context, name string) (err error) {
|
||||
s.initCTX = ctx
|
||||
s.initName = name
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upload wraps the upload method of the next storage
|
||||
func (s *Lazy) Upload(ctx context.Context, req metadata.UploadRequest) (*metadata.UploadResponse, error) {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return next.Upload(ctx, req)
|
||||
}
|
||||
|
||||
// Download wraps the download method of the next storage
|
||||
func (s *Lazy) Download(ctx context.Context, req metadata.DownloadRequest) (*metadata.DownloadResponse, error) {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return next.Download(ctx, req)
|
||||
}
|
||||
|
||||
// SimpleUpload wraps the simple upload method of the next storage
|
||||
func (s *Lazy) SimpleUpload(ctx context.Context, uploadpath string, content []byte) error {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return next.SimpleUpload(ctx, uploadpath, content)
|
||||
}
|
||||
|
||||
// SimpleDownload wraps the simple download method of the next storage
|
||||
func (s *Lazy) SimpleDownload(ctx context.Context, path string) ([]byte, error) {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return next.SimpleDownload(ctx, path)
|
||||
}
|
||||
|
||||
// Delete wraps the delete method of the next storage
|
||||
func (s *Lazy) Delete(ctx context.Context, path string) error {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return next.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// Stat wraps the stat method of the next storage
|
||||
func (s *Lazy) Stat(ctx context.Context, path string) (*provider.ResourceInfo, error) {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return next.Stat(ctx, path)
|
||||
}
|
||||
|
||||
// ReadDir wraps the read directory method of the next storage
|
||||
func (s *Lazy) ReadDir(ctx context.Context, path string) ([]string, error) {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return next.ReadDir(ctx, path)
|
||||
}
|
||||
|
||||
// ListDir wraps the list directory method of the next storage
|
||||
func (s *Lazy) ListDir(ctx context.Context, path string) ([]*provider.ResourceInfo, error) {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return next.ListDir(ctx, path)
|
||||
}
|
||||
|
||||
// CreateSymlink wraps the create symlink method of the next storage
|
||||
func (s *Lazy) CreateSymlink(ctx context.Context, oldname, newname string) error {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return next.CreateSymlink(ctx, oldname, newname)
|
||||
}
|
||||
|
||||
// ResolveSymlink wraps the resolve symlink method of the next storage
|
||||
func (s *Lazy) ResolveSymlink(ctx context.Context, name string) (string, error) {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return next.ResolveSymlink(ctx, name)
|
||||
}
|
||||
|
||||
// MakeDirIfNotExist wraps the make directory if not exist method of the next storage
|
||||
func (s *Lazy) MakeDirIfNotExist(ctx context.Context, name string) error {
|
||||
next, err := s.next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return next.MakeDirIfNotExist(ctx, name)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrStorageInitialization is returned when the storage initialization fails
|
||||
ErrStorageInitialization = errors.New("failed to initialize storage")
|
||||
|
||||
// ErrStorageValidation is returned when the storage configuration is invalid
|
||||
ErrStorageValidation = errors.New("failed to validate storage configuration")
|
||||
)
|
||||
@@ -16,7 +16,7 @@ var (
|
||||
// LatestTag is the latest released version plus the dev meta version.
|
||||
// Will be overwritten by the release pipeline
|
||||
// Needs a manual change for every tagged release
|
||||
LatestTag = "3.0.0+dev"
|
||||
LatestTag = "2.3.0+dev"
|
||||
|
||||
// Date indicates the build date.
|
||||
// This has been removed, it looks like you can only replace static strings with recent go versions
|
||||
|
||||
@@ -34,7 +34,6 @@ type Config struct {
|
||||
Context context.Context `yaml:"-"`
|
||||
|
||||
WriteBufferDuration time.Duration `yaml:"write_buffer_duration" env:"ACTIVITYLOG_WRITE_BUFFER_DURATION" desc:"The duration to wait before flushing the write buffer. This is used to reduce the number of writes to the store." introductionVersion:"%%NEXT%%"`
|
||||
MaxActivities int `yaml:"max_activities" env:"ACTIVITYLOG_MAX_ACTIVITIES" desc:"The maximum number of activities to keep in the store per resource. If the number of activities exceeds this value, the oldest activities will be removed." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
// Events combines the configuration options for the event bus.
|
||||
|
||||
@@ -53,7 +53,6 @@ func DefaultConfig() *config.Config {
|
||||
},
|
||||
},
|
||||
WriteBufferDuration: 10 * time.Second,
|
||||
MaxActivities: 6000,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ func Server(opts ...Option) (http.Service, error) {
|
||||
svc.HistoryClient(options.HistoryClient),
|
||||
svc.ValueClient(options.ValueClient),
|
||||
svc.RegisteredEvents(options.RegisteredEvents),
|
||||
svc.WriteBufferDuration(options.Config.WriteBufferDuration),
|
||||
)
|
||||
if err != nil {
|
||||
return http.Service{}, err
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
const activitylogVersionKey = "activitylog.version"
|
||||
const currentMigrationVersion = "1"
|
||||
|
||||
// RunMigrations checks the activitylog data version and runs migrations if necessary.
|
||||
// It should be called during service startup, after the NATS KeyValue store is initialized.
|
||||
func (a *ActivitylogService) runMigrations(ctx context.Context, kv nats.KeyValue) error {
|
||||
entry, err := kv.Get(activitylogVersionKey)
|
||||
if err == nats.ErrKeyNotFound {
|
||||
a.log.Info().Msg("activitylog version key not found. Running migration to V1...")
|
||||
return a.migrateToV1(ctx, kv)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to get activitylog version from NATS KV store: %w", err)
|
||||
}
|
||||
|
||||
version := string(entry.Value())
|
||||
if version == currentMigrationVersion {
|
||||
a.log.Debug().Str("currentVersion", version).Msg("No migration needed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// If version is something else, it might indicate a future version or an unexpected state.
|
||||
// Add logic here if more complex version handling is needed.
|
||||
return fmt.Errorf("unexpected activitylog version: %s, expected %s or older", version, currentMigrationVersion)
|
||||
}
|
||||
|
||||
// migrateToV1 performs the data migration to version 1.
|
||||
// It iterates over all keys, expecting their values to be JSON arrays of strings.
|
||||
// For each such key, it creates a new key in the format "originalKey.count.timestamp"
|
||||
// and stores the original list of strings (re-marshalled to messagepack) as its value.
|
||||
// Finally, it sets the activitylog.version key to "1".
|
||||
func (a *ActivitylogService) migrateToV1(_ context.Context, kv nats.KeyValue) error {
|
||||
lister, err := kv.ListKeys()
|
||||
if err != nil {
|
||||
return fmt.Errorf("migrateToV1: failed to list keys from NATS KV store: %w", err)
|
||||
}
|
||||
|
||||
migratedCount := 0
|
||||
skippedCount := 0
|
||||
|
||||
keyChan := lister.Keys()
|
||||
defer lister.Stop()
|
||||
|
||||
// keyValueEnvelope is the data structure used by the go micro plugin which was used previously.
|
||||
type keyValueEnvelope struct {
|
||||
Key string `json:"key"`
|
||||
Data []byte `json:"data"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
for key := range keyChan {
|
||||
if key == activitylogVersionKey {
|
||||
skippedCount++
|
||||
continue // Skip the version key itself
|
||||
}
|
||||
|
||||
// Get the original value
|
||||
entry, err := kv.Get(key)
|
||||
if err != nil {
|
||||
a.log.Error().Err(err).Str("key", key).Msg("migrateToV1: Failed to get value for key. Skipping.")
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
valBytes := entry.Value()
|
||||
|
||||
val := keyValueEnvelope{}
|
||||
// Unmarshal the value into the keyValueEnvelope structure
|
||||
if err := json.Unmarshal(valBytes, &val); err != nil {
|
||||
a.log.Error().Err(err).Str("key", key).Msg("migrateToV1: Value for key ss not a keyValueEnvelope. Skipping.")
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// Unmarshal value into a list of strings
|
||||
var activities []RawActivity
|
||||
if err := msgpack.Unmarshal(val.Data, &activities); err != nil {
|
||||
if err := json.Unmarshal(val.Data, &activities); err != nil {
|
||||
// This key's value is not a JSON array of strings. Skip it.
|
||||
a.log.Error().Err(err).Str("key", key).Msg("migrateToV1: Value for key is not a msgback or JSON array of strings. Skipping.")
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the new key
|
||||
newKey := natsKey(val.Key, len(activities))
|
||||
newValue, err := msgpack.Marshal(activities)
|
||||
if err != nil {
|
||||
a.log.Error().Err(err).Str("key", key).Msg("migrateToV1: Failed to marshal activities. Skipping.")
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// Write the value (the list of strings, marshalled as messagepack) under the new key
|
||||
if _, err := kv.Put(newKey, newValue); err != nil {
|
||||
a.log.Error().Err(err).Str("newKey", newKey).Str("key", key).Msg("migrateToV1: Failed to put new key. Skipping.")
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// delete old key, it's no longer needed
|
||||
if err := kv.Delete(key); err != nil {
|
||||
log.Printf("migrateToV1: Failed to delete old key '%s' after migration: %v. Skipping deletion.", key, err)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
migratedCount++
|
||||
}
|
||||
|
||||
// Set the activitylog version to "1" after migration
|
||||
if _, err := kv.PutString(activitylogVersionKey, currentMigrationVersion); err != nil {
|
||||
return fmt.Errorf("migrateToV1: failed to set activitylog version key to '%s' in NATS KV store: %w", currentMigrationVersion, err)
|
||||
}
|
||||
|
||||
a.log.Info().Int("migrated", migratedCount).Int("skipped", skippedCount).Msg("Migration to V1 complete")
|
||||
return nil
|
||||
}
|
||||
@@ -31,7 +31,6 @@ type Options struct {
|
||||
HistoryClient ehsvc.EventHistoryService
|
||||
ValueClient settingssvc.ValueService
|
||||
WriteBufferDuration time.Duration
|
||||
MaxActivities int
|
||||
}
|
||||
|
||||
// Logger configures a logger for the activitylog service
|
||||
@@ -103,3 +102,10 @@ func ValueClient(vs settingssvc.ValueService) Option {
|
||||
o.ValueClient = vs
|
||||
}
|
||||
}
|
||||
|
||||
// WriteBufferDuration sets the write buffer duration
|
||||
func WriteBufferDuration(d time.Duration) Option {
|
||||
return func(o *Options) {
|
||||
o.WriteBufferDuration = d
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/l10n"
|
||||
)
|
||||
@@ -61,13 +61,6 @@ type Actor struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
|
||||
// Sharee represents a share reciever (group or user)
|
||||
type Sharee struct {
|
||||
ID string `json:"id"`
|
||||
DisplayName string `json:"displayName"`
|
||||
ShareType string `json:"shareType"`
|
||||
}
|
||||
|
||||
// ActivityOption allows setting variables for an activity
|
||||
type ActivityOption func(context.Context, gateway.GatewayAPIClient, map[string]interface{}) error
|
||||
|
||||
@@ -211,23 +204,20 @@ func WithSharee(uid *user.UserId, gid *group.GroupId) ActivityOption {
|
||||
case uid != nil:
|
||||
u, err := utils.GetUser(uid, gwc)
|
||||
if err != nil {
|
||||
vars["sharee"] = Sharee{
|
||||
vars["sharee"] = Actor{
|
||||
DisplayName: "DeletedUser",
|
||||
ShareType: "user",
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
vars["sharee"] = Sharee{
|
||||
vars["sharee"] = Actor{
|
||||
ID: uid.GetOpaqueId(),
|
||||
DisplayName: u.GetUsername(),
|
||||
ShareType: "user",
|
||||
}
|
||||
case gid != nil:
|
||||
vars["sharee"] = Sharee{
|
||||
vars["sharee"] = Actor{
|
||||
ID: gid.GetOpaqueId(),
|
||||
DisplayName: "DeletedGroup",
|
||||
ShareType: "group",
|
||||
}
|
||||
r, err := gwc.GetGroup(ctx, &group.GetGroupRequest{GroupId: gid})
|
||||
if err != nil {
|
||||
@@ -238,10 +228,9 @@ func WithSharee(uid *user.UserId, gid *group.GroupId) ActivityOption {
|
||||
return fmt.Errorf("error getting group: %s", r.GetStatus().GetMessage())
|
||||
}
|
||||
|
||||
vars["sharee"] = Sharee{
|
||||
vars["sharee"] = Actor{
|
||||
ID: gid.GetOpaqueId(),
|
||||
DisplayName: r.GetGroup().GetDisplayName(),
|
||||
ShareType: "group",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,14 +2,11 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base32"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -17,13 +14,12 @@ import (
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jellydator/ttlcache/v2"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
@@ -33,7 +29,7 @@ import (
|
||||
)
|
||||
|
||||
// Nats runs into max payload exceeded errors at around 7k activities. Let's keep a buffer.
|
||||
var _maxActivitiesDefault = 6000
|
||||
var _maxActivities = 6000
|
||||
|
||||
// RawActivity represents an activity as it is stored in the activitylog store
|
||||
type RawActivity struct {
|
||||
@@ -47,6 +43,7 @@ type ActivitylogService struct {
|
||||
cfg *config.Config
|
||||
log log.Logger
|
||||
events <-chan events.Event
|
||||
store microstore.Store
|
||||
gws pool.Selectable[gateway.GatewayAPIClient]
|
||||
mux *chi.Mux
|
||||
evHistory ehsvc.EventHistoryService
|
||||
@@ -56,9 +53,6 @@ type ActivitylogService struct {
|
||||
tracer trace.Tracer
|
||||
debouncer *Debouncer
|
||||
parentIdCache *ttlcache.Cache
|
||||
natskv nats.KeyValue
|
||||
|
||||
maxActivities int
|
||||
|
||||
registeredEvents map[string]events.Unmarshaller
|
||||
}
|
||||
@@ -77,12 +71,6 @@ type queueItem struct {
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
type batchInfo struct {
|
||||
key string
|
||||
count int
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
// NewDebouncer returns a new Debouncer instance
|
||||
func NewDebouncer(d time.Duration, f func(id string, ra []RawActivity) error) *Debouncer {
|
||||
return &Debouncer{
|
||||
@@ -140,9 +128,7 @@ func (d *Debouncer) Debounce(id string, ra RawActivity) {
|
||||
|
||||
// New creates a new ActivitylogService
|
||||
func New(opts ...Option) (*ActivitylogService, error) {
|
||||
o := &Options{
|
||||
MaxActivities: _maxActivitiesDefault,
|
||||
}
|
||||
o := &Options{}
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
@@ -151,6 +137,10 @@ func New(opts ...Option) (*ActivitylogService, error) {
|
||||
return nil, errors.New("stream is required")
|
||||
}
|
||||
|
||||
if o.Store == nil {
|
||||
return nil, errors.New("store is required")
|
||||
}
|
||||
|
||||
ch, err := events.Consume(o.Stream, o.Config.Service.Name, o.RegisteredEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -162,41 +152,11 @@ func New(opts ...Option) (*ActivitylogService, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Connect to NATS servers
|
||||
natsOptions := nats.Options{
|
||||
Servers: o.Config.Store.Nodes,
|
||||
}
|
||||
conn, err := natsOptions.Connect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
js, err := conn.JetStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kv, err := js.KeyValue(o.Config.Store.Database)
|
||||
if err != nil {
|
||||
if !errors.Is(err, nats.ErrBucketNotFound) {
|
||||
return nil, errors.Wrapf(err, "Failed to get bucket (%s)", o.Config.Store.Database)
|
||||
}
|
||||
|
||||
kv, err = js.CreateKeyValue(&nats.KeyValueConfig{
|
||||
Bucket: o.Config.Store.Database,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed to create bucket (%s)", o.Config.Store.Database)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &ActivitylogService{
|
||||
log: o.Logger,
|
||||
cfg: o.Config,
|
||||
events: ch,
|
||||
store: o.Store,
|
||||
gws: o.GatewaySelector,
|
||||
mux: o.Mux,
|
||||
evHistory: o.HistoryClient,
|
||||
@@ -206,16 +166,8 @@ func New(opts ...Option) (*ActivitylogService, error) {
|
||||
tp: o.TraceProvider,
|
||||
tracer: o.TraceProvider.Tracer("github.com/opencloud-eu/opencloud/services/activitylog/pkg/service"),
|
||||
parentIdCache: cache,
|
||||
maxActivities: o.Config.MaxActivities,
|
||||
natskv: kv,
|
||||
}
|
||||
s.debouncer = NewDebouncer(o.Config.WriteBufferDuration, s.storeActivity)
|
||||
|
||||
// run migrations
|
||||
err = s.runMigrations(context.Background(), kv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.debouncer = NewDebouncer(o.WriteBufferDuration, s.storeActivity)
|
||||
|
||||
s.mux.Get("/graph/v1beta1/extensions/org.libregraph/activities", s.HandleGetItemActivities)
|
||||
|
||||
@@ -298,7 +250,7 @@ func (a *ActivitylogService) AddActivity(initRef *provider.Reference, parentId *
|
||||
ctx, span = a.tracer.Start(ctx, "AddActivity")
|
||||
defer span.End()
|
||||
|
||||
return a.addActivity(ctx, initRef, parentId, eventID, timestamp, func(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) {
|
||||
return a.addActivity(ctx, initRef, parentId, eventID, timestamp, func(ref *provider.Reference) (*provider.ResourceInfo, error) {
|
||||
return utils.GetResource(ctx, ref, gwc)
|
||||
})
|
||||
}
|
||||
@@ -333,10 +285,10 @@ func (a *ActivitylogService) AddActivityTrashed(resourceID *provider.ResourceId,
|
||||
}
|
||||
|
||||
var span trace.Span
|
||||
ctx, span = a.tracer.Start(ctx, "AddActivityTrashed")
|
||||
ctx, span = a.tracer.Start(ctx, "AddActivity")
|
||||
defer span.End()
|
||||
|
||||
return a.addActivity(ctx, ref, parentId, eventID, timestamp, func(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) {
|
||||
return a.addActivity(ctx, ref, parentId, eventID, timestamp, func(ref *provider.Reference) (*provider.ResourceInfo, error) {
|
||||
return utils.GetResource(ctx, ref, gwc)
|
||||
})
|
||||
}
|
||||
@@ -391,8 +343,10 @@ func (a *ActivitylogService) RemoveActivities(rid *provider.ResourceId, toDelete
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = a.natskv.Put(storagespace.FormatResourceID(rid), b)
|
||||
return err
|
||||
return a.store.Write(µstore.Record{
|
||||
Key: storagespace.FormatResourceID(rid),
|
||||
Value: b,
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveResource removes the resource from the store
|
||||
@@ -404,53 +358,45 @@ func (a *ActivitylogService) RemoveResource(rid *provider.ResourceId) error {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
return a.natskv.Delete(storagespace.FormatResourceID(rid))
|
||||
return a.store.Delete(storagespace.FormatResourceID(rid))
|
||||
}
|
||||
|
||||
func (a *ActivitylogService) activities(rid *provider.ResourceId) ([]RawActivity, error) {
|
||||
resourceID := storagespace.FormatResourceID(rid)
|
||||
|
||||
glob := fmt.Sprintf("%s.>", base32.StdEncoding.EncodeToString([]byte(resourceID)))
|
||||
|
||||
watcher, err := a.natskv.Watch(glob, nats.IgnoreDeletes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
records, err := a.store.Read(resourceID)
|
||||
if err != nil && err != microstore.ErrNotFound {
|
||||
return nil, fmt.Errorf("could not read activities: %w", err)
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
return []RawActivity{}, nil
|
||||
}
|
||||
defer watcher.Stop()
|
||||
|
||||
var activities []RawActivity
|
||||
for update := range watcher.Updates() {
|
||||
if update == nil {
|
||||
break
|
||||
if err := msgpack.Unmarshal(records[0].Value, &activities); err != nil {
|
||||
a.log.Debug().Err(err).Str("resourceID", resourceID).Msg("could not unmarshal messagepack, trying json")
|
||||
if err := json.Unmarshal(records[0].Value, &activities); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal activities: %w", err)
|
||||
}
|
||||
|
||||
var batchActivities []RawActivity
|
||||
if err := msgpack.Unmarshal(update.Value(), &batchActivities); err != nil {
|
||||
a.log.Debug().Err(err).Str("resourceID", resourceID).Msg("could not unmarshal messagepack, trying json")
|
||||
}
|
||||
activities = append(activities, batchActivities...)
|
||||
}
|
||||
|
||||
return activities, nil
|
||||
}
|
||||
|
||||
// note: getResource is abstracted to allow unit testing, in general this will just be utils.GetResource
|
||||
func (a *ActivitylogService) addActivity(ctx context.Context, initRef *provider.Reference, parentId *provider.ResourceId, eventID string, timestamp time.Time, getResource func(context.Context, *provider.Reference) (*provider.ResourceInfo, error)) error {
|
||||
func (a *ActivitylogService) addActivity(ctx context.Context, initRef *provider.Reference, parentId *provider.ResourceId, eventID string, timestamp time.Time, getResource func(*provider.Reference) (*provider.ResourceInfo, error)) error {
|
||||
var (
|
||||
err error
|
||||
depth int
|
||||
ref = initRef
|
||||
)
|
||||
ctx, span := a.tracer.Start(ctx, "addActivity")
|
||||
defer span.End()
|
||||
for {
|
||||
var info *provider.ResourceInfo
|
||||
id := ref.GetResourceId()
|
||||
if ref.Path != "" {
|
||||
// Path based reference, we need to resolve the resource id
|
||||
ctx, span = a.tracer.Start(ctx, "addActivity.getResource")
|
||||
info, err = getResource(ctx, ref)
|
||||
span.End()
|
||||
info, err = getResource(ref)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get resource info: %w", err)
|
||||
}
|
||||
@@ -461,15 +407,17 @@ func (a *ActivitylogService) addActivity(ctx context.Context, initRef *provider.
|
||||
}
|
||||
|
||||
key := storagespace.FormatResourceID(id)
|
||||
_, span := a.tracer.Start(ctx, "queueStoreActivity")
|
||||
a.debouncer.Debounce(key, RawActivity{
|
||||
EventID: eventID,
|
||||
Depth: depth,
|
||||
Timestamp: timestamp,
|
||||
})
|
||||
span.End()
|
||||
|
||||
if id.OpaqueId == id.SpaceId {
|
||||
// we are at the root of the space, no need to go further
|
||||
break
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if parent id is cached
|
||||
@@ -478,8 +426,8 @@ func (a *ActivitylogService) addActivity(ctx context.Context, initRef *provider.
|
||||
if parentId == nil {
|
||||
if v, err := a.parentIdCache.Get(key); err != nil {
|
||||
if info == nil {
|
||||
ctx, span := a.tracer.Start(ctx, "addActivity.getResource parent")
|
||||
info, err = getResource(ctx, ref)
|
||||
_, span = a.tracer.Start(ctx, "getResource")
|
||||
info, err = getResource(ref)
|
||||
span.End()
|
||||
if err != nil || info.GetParentId() == nil || info.GetParentId().GetOpaqueId() == "" {
|
||||
return fmt.Errorf("could not get parent id: %w", err)
|
||||
@@ -498,8 +446,6 @@ func (a *ActivitylogService) addActivity(ctx context.Context, initRef *provider.
|
||||
ref = &provider.Reference{ResourceId: parentId}
|
||||
parentId = nil // reset parent id so it's not reused in the next iteration
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ActivitylogService) storeActivity(resourceID string, activities []RawActivity) error {
|
||||
@@ -508,110 +454,43 @@ func (a *ActivitylogService) storeActivity(resourceID string, activities []RawAc
|
||||
|
||||
ctx, span := a.tracer.Start(context.Background(), "storeActivity")
|
||||
defer span.End()
|
||||
_, subspan := a.tracer.Start(ctx, "store.Read")
|
||||
records, err := a.store.Read(resourceID)
|
||||
if err != nil && err != microstore.ErrNotFound {
|
||||
return err
|
||||
}
|
||||
subspan.End()
|
||||
|
||||
_, subspan := a.tracer.Start(ctx, "storeActivity.Marshal")
|
||||
_, subspan = a.tracer.Start(ctx, "Unmarshal")
|
||||
var existingActivities []RawActivity
|
||||
if len(records) > 0 {
|
||||
if err := msgpack.Unmarshal(records[0].Value, &existingActivities); err != nil {
|
||||
a.log.Debug().Err(err).Str("resourceID", resourceID).Msg("could not unmarshal messagepack, trying json")
|
||||
if err := json.Unmarshal(records[0].Value, &existingActivities); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
subspan.End()
|
||||
|
||||
if l := len(existingActivities) + len(activities); l >= _maxActivities {
|
||||
start := min(len(existingActivities), l-_maxActivities+1)
|
||||
existingActivities = existingActivities[start:]
|
||||
}
|
||||
|
||||
activities = append(existingActivities, activities...)
|
||||
|
||||
_, subspan = a.tracer.Start(ctx, "Unmarshal")
|
||||
b, err := msgpack.Marshal(activities)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subspan.End()
|
||||
|
||||
_, subspan = a.tracer.Start(ctx, "storeActivity.natskv.Put")
|
||||
key := natsKey(resourceID, len(activities))
|
||||
_, err = a.natskv.Put(key, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subspan.End()
|
||||
|
||||
ctx, subspan = a.tracer.Start(ctx, "storeActivity.enforceMaxActivities")
|
||||
a.enforceMaxActivities(ctx, resourceID)
|
||||
subspan.End()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ActivitylogService) enforceMaxActivities(ctx context.Context, resourceID string) {
|
||||
if a.maxActivities <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s.>", base32.StdEncoding.EncodeToString([]byte(resourceID)))
|
||||
|
||||
_, subspan := a.tracer.Start(ctx, "enforceMaxActivities.watch")
|
||||
watcher, err := a.natskv.Watch(key, nats.IgnoreDeletes())
|
||||
if err != nil {
|
||||
a.log.Error().Err(err).Str("resourceID", resourceID).Msg("could not watch")
|
||||
return
|
||||
}
|
||||
defer watcher.Stop()
|
||||
|
||||
var keys []string
|
||||
for update := range watcher.Updates() {
|
||||
if update == nil {
|
||||
break
|
||||
}
|
||||
|
||||
var batchActivities []RawActivity
|
||||
if err := msgpack.Unmarshal(update.Value(), &batchActivities); err != nil {
|
||||
a.log.Debug().Err(err).Str("resourceID", resourceID).Msg("could not unmarshal messagepack, trying json")
|
||||
}
|
||||
keys = append(keys, update.Key())
|
||||
}
|
||||
subspan.End()
|
||||
|
||||
_, subspan = a.tracer.Start(ctx, "enforceMaxActivities.compile")
|
||||
// Parse keys into batches
|
||||
batches := make([]batchInfo, 0)
|
||||
var activitiesCount int
|
||||
for _, k := range keys {
|
||||
parts := strings.SplitN(k, ".", 3)
|
||||
if len(parts) < 3 {
|
||||
a.log.Warn().Str("key", k).Msg("skipping key, not enough parts")
|
||||
continue
|
||||
}
|
||||
|
||||
c, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
a.log.Warn().Str("key", k).Msg("skipping key, can not parse count")
|
||||
continue
|
||||
}
|
||||
|
||||
// parse timestamp
|
||||
nano, err := strconv.ParseInt(parts[2], 10, 64)
|
||||
if err != nil {
|
||||
a.log.Warn().Str("key", k).Msg("skipping key, can not parse timestamp")
|
||||
continue
|
||||
}
|
||||
|
||||
batches = append(batches, batchInfo{
|
||||
key: k,
|
||||
count: c,
|
||||
timestamp: time.Unix(0, nano),
|
||||
})
|
||||
activitiesCount += c
|
||||
}
|
||||
|
||||
// sort batches by timestamp
|
||||
sort.Slice(batches, func(i, j int) bool {
|
||||
return batches[i].timestamp.Before(batches[j].timestamp)
|
||||
return a.store.Write(µstore.Record{
|
||||
Key: resourceID,
|
||||
Value: b,
|
||||
})
|
||||
subspan.End()
|
||||
|
||||
_, subspan = a.tracer.Start(ctx, "enforceMaxActivities.delete")
|
||||
// remove oldest keys until we are at max activities
|
||||
for _, b := range batches {
|
||||
if activitiesCount-b.count < a.maxActivities {
|
||||
break
|
||||
}
|
||||
|
||||
activitiesCount -= b.count
|
||||
err = a.natskv.Delete(b.key)
|
||||
if err != nil {
|
||||
a.log.Error().Err(err).Str("key", b.key).Msg("could not delete key")
|
||||
break
|
||||
}
|
||||
}
|
||||
subspan.End()
|
||||
}
|
||||
|
||||
func toRef(r *provider.ResourceId) *provider.Reference {
|
||||
@@ -652,10 +531,3 @@ func (a *ActivitylogService) removeCachedParentID(ref *provider.Reference) {
|
||||
a.log.Error().Interface("event", ref).Err(err).Msg("could not delete parent id cache")
|
||||
}
|
||||
}
|
||||
|
||||
func natsKey(resourceID string, activitiesCount int) string {
|
||||
return fmt.Sprintf("%s.%d.%d",
|
||||
base32.StdEncoding.EncodeToString([]byte(resourceID)),
|
||||
activitiesCount,
|
||||
time.Now().UnixNano())
|
||||
}
|
||||
|
||||
@@ -2,101 +2,30 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
nserver "github.com/nats-io/nats-server/v2/server"
|
||||
"github.com/jellydator/ttlcache/v2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config"
|
||||
eventsmocks "github.com/opencloud-eu/reva/v2/pkg/events/mocks"
|
||||
"github.com/test-go/testify/mock"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/store"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
)
|
||||
|
||||
var (
|
||||
server *nserver.Server
|
||||
tmpdir string
|
||||
)
|
||||
|
||||
func getFreeLocalhostPort() (int, error) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
port := l.Addr().(*net.TCPAddr).Port
|
||||
_ = l.Close() // Close the listener immediately to free the port
|
||||
return port, nil
|
||||
}
|
||||
|
||||
// Spawn a nats server and a JetStream instance for the duration of the test suite.
|
||||
// The different tests need to make sure to use different databases to avoid conflicts.
|
||||
var _ = SynchronizedBeforeSuite(func() {
|
||||
port, err := getFreeLocalhostPort()
|
||||
server, err = nserver.NewServer(&nserver.Options{
|
||||
Port: port,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
tmpdir, err = os.MkdirTemp("", "activitylog-test")
|
||||
natsdir := filepath.Join(tmpdir, "nats-js")
|
||||
jsConf := &nserver.JetStreamConfig{
|
||||
StoreDir: natsdir,
|
||||
}
|
||||
// first start NATS
|
||||
go server.Start()
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// second start JetStream
|
||||
err = server.EnableJetStream(jsConf)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}, func() {})
|
||||
|
||||
var _ = SynchronizedAfterSuite(func() {
|
||||
server.Shutdown()
|
||||
_ = os.RemoveAll(tmpdir)
|
||||
}, func() {})
|
||||
|
||||
var _ = Describe("ActivitylogService", func() {
|
||||
var (
|
||||
alog *ActivitylogService
|
||||
getResource func(_ context.Context, ref *provider.Reference) (*provider.ResourceInfo, error)
|
||||
writebufferduration = 100 * time.Millisecond
|
||||
alog *ActivitylogService
|
||||
getResource func(ref *provider.Reference) (*provider.ResourceInfo, error)
|
||||
)
|
||||
|
||||
JustBeforeEach(func() {
|
||||
var err error
|
||||
stream := &eventsmocks.Stream{}
|
||||
stream.EXPECT().Consume(mock.Anything, mock.Anything).Return(nil, nil)
|
||||
alog, err = New(
|
||||
Config(&config.Config{
|
||||
Service: config.Service{
|
||||
Name: "activitylog-test",
|
||||
},
|
||||
Store: config.Store{
|
||||
Store: "nats-js-kv",
|
||||
Nodes: []string{server.Addr().String()},
|
||||
Database: "activitylog-test-" + uuid.New().String(),
|
||||
},
|
||||
MaxActivities: 4,
|
||||
WriteBufferDuration: writebufferduration,
|
||||
}),
|
||||
Stream(stream),
|
||||
TraceProvider(noop.NewTracerProvider()),
|
||||
Mux(chi.NewMux()),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
Context("with a noop debouncer", func() {
|
||||
BeforeEach(func() {
|
||||
writebufferduration = 0
|
||||
alog = &ActivitylogService{
|
||||
store: store.Create(),
|
||||
tracer: noop.NewTracerProvider().Tracer("test"),
|
||||
parentIdCache: ttlcache.NewCache(),
|
||||
}
|
||||
alog.debouncer = NewDebouncer(0, alog.storeActivity)
|
||||
})
|
||||
|
||||
Describe("AddActivity", func() {
|
||||
@@ -147,8 +76,8 @@ var _ = Describe("ActivitylogService", func() {
|
||||
for _, tc := range testCases {
|
||||
tc := tc // capture range variable
|
||||
Context(tc.Name, func() {
|
||||
JustBeforeEach(func() {
|
||||
getResource = func(_ context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) {
|
||||
BeforeEach(func() {
|
||||
getResource = func(ref *provider.Reference) (*provider.ResourceInfo, error) {
|
||||
return tc.Tree[ref.GetResourceId().GetOpaqueId()], nil
|
||||
}
|
||||
|
||||
@@ -178,92 +107,30 @@ var _ = Describe("ActivitylogService", func() {
|
||||
"spaceid": resourceInfo("spaceid", "spaceid"),
|
||||
}
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
writebufferduration = 100 * time.Millisecond
|
||||
alog = &ActivitylogService{
|
||||
store: store.Create(),
|
||||
tracer: noop.NewTracerProvider().Tracer("test"),
|
||||
parentIdCache: ttlcache.NewCache(),
|
||||
}
|
||||
alog.debouncer = NewDebouncer(100*time.Millisecond, alog.storeActivity)
|
||||
})
|
||||
|
||||
Describe("addActivity", func() {
|
||||
var (
|
||||
getResource = func(_ context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) {
|
||||
return tree[ref.GetResourceId().GetOpaqueId()], nil
|
||||
}
|
||||
)
|
||||
It("should debounce activities", func() {
|
||||
getResource = func(ref *provider.Reference) (*provider.ResourceInfo, error) {
|
||||
return tree[ref.GetResourceId().GetOpaqueId()], nil
|
||||
}
|
||||
|
||||
It("debounces activities", func() {
|
||||
err := alog.addActivity(context.Background(), reference("base"), nil, "activity1", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = alog.addActivity(context.Background(), reference("base"), nil, "activity2", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err := alog.addActivity(context.Background(), reference("base"), nil, "activity1", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = alog.addActivity(context.Background(), reference("base"), nil, "activity2", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
activities, err := alog.Activities(resourceID("base"))
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(activities).To(ConsistOf(activitites("activity1", 0, "activity2", 0)))
|
||||
}).Should(Succeed())
|
||||
})
|
||||
|
||||
It("adheres to the MaxActivities setting", func() {
|
||||
err := alog.addActivity(context.Background(), reference("base"), nil, "activity1", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Eventually(func(g Gomega) {
|
||||
activities, err := alog.Activities(resourceID("base"))
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(len(activities)).To(Equal(1))
|
||||
}).Should(Succeed())
|
||||
|
||||
err = alog.addActivity(context.Background(), reference("base"), nil, "activity2", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Eventually(func(g Gomega) {
|
||||
activities, err := alog.Activities(resourceID("base"))
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(len(activities)).To(Equal(2))
|
||||
}).Should(Succeed())
|
||||
|
||||
err = alog.addActivity(context.Background(), reference("base"), nil, "activity3", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = alog.addActivity(context.Background(), reference("base"), nil, "activity4", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = alog.addActivity(context.Background(), reference("base"), nil, "activity5", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
activities, err := alog.Activities(resourceID("base"))
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(activities).To(ConsistOf(activitites("activity2", 0, "activity3", 0, "activity4", 0, "activity5", 0)))
|
||||
}).Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Activities", func() {
|
||||
It("combines multiple batches", func() {
|
||||
getResource = func(_ context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) {
|
||||
return tree[ref.GetResourceId().GetOpaqueId()], nil
|
||||
}
|
||||
|
||||
err := alog.addActivity(context.Background(), reference("base"), nil, "activity1", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = alog.addActivity(context.Background(), reference("base"), nil, "activity2", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
activities, err := alog.Activities(resourceID("base"))
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(activities).To(ConsistOf(activitites("activity1", 0, "activity2", 0)))
|
||||
}).Should(Succeed())
|
||||
|
||||
err = alog.addActivity(context.Background(), reference("base"), nil, "activity3", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = alog.addActivity(context.Background(), reference("base"), nil, "activity4", time.Time{}, getResource)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
activities, err := alog.Activities(resourceID("base"))
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(activities).To(ConsistOf(activitites("activity1", 0, "activity2", 0, "activity3", 0, "activity4", 0)))
|
||||
}).Should(Succeed())
|
||||
})
|
||||
Eventually(func(g Gomega) {
|
||||
activities, err := alog.Activities(resourceID("base"))
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(activities).To(ConsistOf(activitites("activity1", 0, "activity2", 0)))
|
||||
}).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -129,8 +129,9 @@ The default language can be defined via the `OC_DEFAULT_LANGUAGE` environment va
|
||||
|
||||
Unified Roles are roles granted a user for sharing and can be enabled or disabled. A CLI command is provided to list existing roles and their state among other data.
|
||||
|
||||
::: info
|
||||
{{< hint info >}}
|
||||
Note that a disabled role does not lose previously assigned permissions. It only means that the role is not available for new assignments.
|
||||
{{< /hint >}}
|
||||
|
||||
The following roles are **enabled** by default:
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
|
||||
svc "github.com/opencloud-eu/opencloud/services/graph/pkg/service/v0"
|
||||
)
|
||||
|
||||
// DriveItemPermissionsProvider is an autogenerated mock type for the DriveItemPermissionsProvider type
|
||||
@@ -296,9 +294,9 @@ func (_c *DriveItemPermissionsProvider_Invite_Call) RunAndReturn(run func(contex
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListPermissions provides a mock function with given fields: ctx, itemID, queryOptions
|
||||
func (_m *DriveItemPermissionsProvider) ListPermissions(ctx context.Context, itemID *providerv1beta1.ResourceId, queryOptions svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) {
|
||||
ret := _m.Called(ctx, itemID, queryOptions)
|
||||
// ListPermissions provides a mock function with given fields: ctx, itemID, listFederatedRoles, selectedAttrs
|
||||
func (_m *DriveItemPermissionsProvider) ListPermissions(ctx context.Context, itemID *providerv1beta1.ResourceId, listFederatedRoles bool, selectedAttrs map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error) {
|
||||
ret := _m.Called(ctx, itemID, listFederatedRoles, selectedAttrs)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListPermissions")
|
||||
@@ -306,17 +304,17 @@ func (_m *DriveItemPermissionsProvider) ListPermissions(ctx context.Context, ite
|
||||
|
||||
var r0 libregraph.CollectionOfPermissionsWithAllowedValues
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error)); ok {
|
||||
return rf(ctx, itemID, queryOptions)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, bool, map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error)); ok {
|
||||
return rf(ctx, itemID, listFederatedRoles, selectedAttrs)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) libregraph.CollectionOfPermissionsWithAllowedValues); ok {
|
||||
r0 = rf(ctx, itemID, queryOptions)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, bool, map[string]struct{}) libregraph.CollectionOfPermissionsWithAllowedValues); ok {
|
||||
r0 = rf(ctx, itemID, listFederatedRoles, selectedAttrs)
|
||||
} else {
|
||||
r0 = ret.Get(0).(libregraph.CollectionOfPermissionsWithAllowedValues)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) error); ok {
|
||||
r1 = rf(ctx, itemID, queryOptions)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, bool, map[string]struct{}) error); ok {
|
||||
r1 = rf(ctx, itemID, listFederatedRoles, selectedAttrs)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -332,14 +330,15 @@ type DriveItemPermissionsProvider_ListPermissions_Call struct {
|
||||
// ListPermissions is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - itemID *providerv1beta1.ResourceId
|
||||
// - queryOptions svc.ListPermissionsQueryOptions
|
||||
func (_e *DriveItemPermissionsProvider_Expecter) ListPermissions(ctx interface{}, itemID interface{}, queryOptions interface{}) *DriveItemPermissionsProvider_ListPermissions_Call {
|
||||
return &DriveItemPermissionsProvider_ListPermissions_Call{Call: _e.mock.On("ListPermissions", ctx, itemID, queryOptions)}
|
||||
// - listFederatedRoles bool
|
||||
// - selectedAttrs map[string]struct{}
|
||||
func (_e *DriveItemPermissionsProvider_Expecter) ListPermissions(ctx interface{}, itemID interface{}, listFederatedRoles interface{}, selectedAttrs interface{}) *DriveItemPermissionsProvider_ListPermissions_Call {
|
||||
return &DriveItemPermissionsProvider_ListPermissions_Call{Call: _e.mock.On("ListPermissions", ctx, itemID, listFederatedRoles, selectedAttrs)}
|
||||
}
|
||||
|
||||
func (_c *DriveItemPermissionsProvider_ListPermissions_Call) Run(run func(ctx context.Context, itemID *providerv1beta1.ResourceId, queryOptions svc.ListPermissionsQueryOptions)) *DriveItemPermissionsProvider_ListPermissions_Call {
|
||||
func (_c *DriveItemPermissionsProvider_ListPermissions_Call) Run(run func(ctx context.Context, itemID *providerv1beta1.ResourceId, listFederatedRoles bool, selectedAttrs map[string]struct{})) *DriveItemPermissionsProvider_ListPermissions_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*providerv1beta1.ResourceId), args[2].(svc.ListPermissionsQueryOptions))
|
||||
run(args[0].(context.Context), args[1].(*providerv1beta1.ResourceId), args[2].(bool), args[3].(map[string]struct{}))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -349,14 +348,14 @@ func (_c *DriveItemPermissionsProvider_ListPermissions_Call) Return(_a0 libregra
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *DriveItemPermissionsProvider_ListPermissions_Call) RunAndReturn(run func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error)) *DriveItemPermissionsProvider_ListPermissions_Call {
|
||||
func (_c *DriveItemPermissionsProvider_ListPermissions_Call) RunAndReturn(run func(context.Context, *providerv1beta1.ResourceId, bool, map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error)) *DriveItemPermissionsProvider_ListPermissions_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListSpaceRootPermissions provides a mock function with given fields: ctx, driveID, queryOptions
|
||||
func (_m *DriveItemPermissionsProvider) ListSpaceRootPermissions(ctx context.Context, driveID *providerv1beta1.ResourceId, queryOptions svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) {
|
||||
ret := _m.Called(ctx, driveID, queryOptions)
|
||||
// ListSpaceRootPermissions provides a mock function with given fields: ctx, driveID, selectedAttrs
|
||||
func (_m *DriveItemPermissionsProvider) ListSpaceRootPermissions(ctx context.Context, driveID *providerv1beta1.ResourceId, selectedAttrs map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error) {
|
||||
ret := _m.Called(ctx, driveID, selectedAttrs)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListSpaceRootPermissions")
|
||||
@@ -364,17 +363,17 @@ func (_m *DriveItemPermissionsProvider) ListSpaceRootPermissions(ctx context.Con
|
||||
|
||||
var r0 libregraph.CollectionOfPermissionsWithAllowedValues
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error)); ok {
|
||||
return rf(ctx, driveID, queryOptions)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error)); ok {
|
||||
return rf(ctx, driveID, selectedAttrs)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) libregraph.CollectionOfPermissionsWithAllowedValues); ok {
|
||||
r0 = rf(ctx, driveID, queryOptions)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, map[string]struct{}) libregraph.CollectionOfPermissionsWithAllowedValues); ok {
|
||||
r0 = rf(ctx, driveID, selectedAttrs)
|
||||
} else {
|
||||
r0 = ret.Get(0).(libregraph.CollectionOfPermissionsWithAllowedValues)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) error); ok {
|
||||
r1 = rf(ctx, driveID, queryOptions)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, map[string]struct{}) error); ok {
|
||||
r1 = rf(ctx, driveID, selectedAttrs)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -390,14 +389,14 @@ type DriveItemPermissionsProvider_ListSpaceRootPermissions_Call struct {
|
||||
// ListSpaceRootPermissions is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - driveID *providerv1beta1.ResourceId
|
||||
// - queryOptions svc.ListPermissionsQueryOptions
|
||||
func (_e *DriveItemPermissionsProvider_Expecter) ListSpaceRootPermissions(ctx interface{}, driveID interface{}, queryOptions interface{}) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call {
|
||||
return &DriveItemPermissionsProvider_ListSpaceRootPermissions_Call{Call: _e.mock.On("ListSpaceRootPermissions", ctx, driveID, queryOptions)}
|
||||
// - selectedAttrs map[string]struct{}
|
||||
func (_e *DriveItemPermissionsProvider_Expecter) ListSpaceRootPermissions(ctx interface{}, driveID interface{}, selectedAttrs interface{}) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call {
|
||||
return &DriveItemPermissionsProvider_ListSpaceRootPermissions_Call{Call: _e.mock.On("ListSpaceRootPermissions", ctx, driveID, selectedAttrs)}
|
||||
}
|
||||
|
||||
func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) Run(run func(ctx context.Context, driveID *providerv1beta1.ResourceId, queryOptions svc.ListPermissionsQueryOptions)) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call {
|
||||
func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) Run(run func(ctx context.Context, driveID *providerv1beta1.ResourceId, selectedAttrs map[string]struct{})) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*providerv1beta1.ResourceId), args[2].(svc.ListPermissionsQueryOptions))
|
||||
run(args[0].(context.Context), args[1].(*providerv1beta1.ResourceId), args[2].(map[string]struct{}))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -407,7 +406,7 @@ func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) Return(_a0
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) RunAndReturn(run func(context.Context, *providerv1beta1.ResourceId, svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error)) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call {
|
||||
func (_c *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call) RunAndReturn(run func(context.Context, *providerv1beta1.ResourceId, map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error)) *DriveItemPermissionsProvider_ListSpaceRootPermissions_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/odata"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
)
|
||||
@@ -82,7 +81,7 @@ func (i *CS3) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libr
|
||||
return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
|
||||
}
|
||||
|
||||
search, err := odata.GetSearchValues(oreq.Query)
|
||||
search, err := GetSearchValues(oreq.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,7 +132,7 @@ func (i *CS3) GetGroups(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib
|
||||
return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
|
||||
}
|
||||
|
||||
search, err := odata.GetSearchValues(oreq.Query)
|
||||
search, err := GetSearchValues(oreq.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/odata"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -564,7 +563,7 @@ func (i *LDAP) GetUser(ctx context.Context, nameOrID string, oreq *godata.GoData
|
||||
}
|
||||
}
|
||||
|
||||
exp, err := odata.GetExpandValues(oreq.Query)
|
||||
exp, err := GetExpandValues(oreq.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -594,12 +593,12 @@ func (i *LDAP) FilterUsers(ctx context.Context, oreq *godata.GoDataRequest, filt
|
||||
return nil, err
|
||||
}
|
||||
|
||||
search, err := odata.GetSearchValues(oreq.Query)
|
||||
search, err := GetSearchValues(oreq.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exp, err := odata.GetExpandValues(oreq.Query)
|
||||
exp, err := GetExpandValues(oreq.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/odata"
|
||||
)
|
||||
|
||||
type groupAttributeMap struct {
|
||||
@@ -60,17 +59,17 @@ func (i *LDAP) GetGroups(ctx context.Context, oreq *godata.GoDataRequest) ([]*li
|
||||
logger := i.logger.SubloggerWithRequestID(ctx)
|
||||
logger.Debug().Str("backend", "ldap").Msg("GetGroups")
|
||||
|
||||
search, err := odata.GetSearchValues(oreq.Query)
|
||||
search, err := GetSearchValues(oreq.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var expandMembers bool
|
||||
exp, err := odata.GetExpandValues(oreq.Query)
|
||||
exp, err := GetExpandValues(oreq.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sel, err := odata.GetSelectValues(oreq.Query)
|
||||
sel, err := GetSelectValues(oreq.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -147,7 +146,7 @@ func (i *LDAP) GetGroupMembers(ctx context.Context, groupID string, req *godata.
|
||||
logger := i.logger.SubloggerWithRequestID(ctx)
|
||||
logger.Debug().Str("backend", "ldap").Msg("GetGroupMembers")
|
||||
|
||||
exp, err := odata.GetExpandValues(req.Query)
|
||||
exp, err := GetExpandValues(req.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -157,7 +156,7 @@ func (i *LDAP) GetGroupMembers(ctx context.Context, groupID string, req *godata.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
searchTerm, err := odata.GetSearchValues(req.Query)
|
||||
searchTerm, err := GetSearchValues(req.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
63
services/graph/pkg/identity/odata.go
Normal file
63
services/graph/pkg/identity/odata.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package identity
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/CiscoM31/godata"
|
||||
)
|
||||
|
||||
// GetExpandValues extracts the values of the $expand query parameter and
|
||||
// returns them in a []string, rejects any $expand value that consists of more
|
||||
// than just a single path segment
|
||||
func GetExpandValues(req *godata.GoDataQuery) ([]string, error) {
|
||||
if req == nil || req.Expand == nil {
|
||||
return []string{}, nil
|
||||
}
|
||||
expand := make([]string, 0, len(req.Expand.ExpandItems))
|
||||
for _, item := range req.Expand.ExpandItems {
|
||||
if item.Filter != nil || item.At != nil || item.Search != nil ||
|
||||
item.OrderBy != nil || item.Skip != nil || item.Top != nil ||
|
||||
item.Select != nil || item.Compute != nil || item.Expand != nil ||
|
||||
item.Levels != 0 {
|
||||
return []string{}, godata.NotImplementedError("options for $expand not supported")
|
||||
}
|
||||
if len(item.Path) > 1 {
|
||||
return []string{}, godata.NotImplementedError("multiple segments in $expand not supported")
|
||||
}
|
||||
expand = append(expand, item.Path[0].Value)
|
||||
}
|
||||
return expand, nil
|
||||
}
|
||||
|
||||
// GetSelectValues extracts the values of the $select query parameter and
|
||||
// returns them in a []string, rejects any $select value that consists of more
|
||||
// than just a single path segment
|
||||
func GetSelectValues(req *godata.GoDataQuery) ([]string, error) {
|
||||
if req == nil || req.Select == nil {
|
||||
return []string{}, nil
|
||||
}
|
||||
sel := make([]string, 0, len(req.Select.SelectItems))
|
||||
for _, item := range req.Select.SelectItems {
|
||||
if len(item.Segments) > 1 {
|
||||
return []string{}, godata.NotImplementedError("multiple segments in $select not supported")
|
||||
}
|
||||
sel = append(sel, item.Segments[0].Value)
|
||||
}
|
||||
return sel, nil
|
||||
}
|
||||
|
||||
// GetSearchValues extracts the value of the $search query parameter and returns
|
||||
// it as a string. Rejects any search query that is more than just a simple string
|
||||
func GetSearchValues(req *godata.GoDataQuery) (string, error) {
|
||||
if req == nil || req.Search == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Only allow simple search queries for now
|
||||
if len(req.Search.Tree.Children) != 0 {
|
||||
return "", godata.NotImplementedError("complex search queries are not supported")
|
||||
}
|
||||
|
||||
searchValue := strings.Trim(req.Search.Tree.Token.Value, "\"")
|
||||
return searchValue, nil
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package odata
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/CiscoM31/godata"
|
||||
)
|
||||
|
||||
// GetExpandValues extracts the values of the $expand query parameter and
|
||||
// returns them in a []string, rejecting any $expand value that consists of more
|
||||
// than just a single path segment.
|
||||
func GetExpandValues(req *godata.GoDataQuery) ([]string, error) {
|
||||
if req == nil || req.Expand == nil {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
var expand []string
|
||||
for _, item := range req.Expand.ExpandItems {
|
||||
paths, err := collectExpandPaths(item, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expand = append(expand, paths...)
|
||||
}
|
||||
|
||||
return expand, nil
|
||||
}
|
||||
|
||||
// collectExpandPaths recursively collects all valid expand paths from the given item.
|
||||
func collectExpandPaths(item *godata.ExpandItem, prefix string) ([]string, error) {
|
||||
if err := validateExpandItem(item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build the current path
|
||||
currentPath := prefix
|
||||
if len(item.Path) > 1 {
|
||||
return nil, godata.NotImplementedError("multiple segments in $expand not supported")
|
||||
}
|
||||
if len(item.Path) == 1 {
|
||||
if currentPath == "" {
|
||||
currentPath = item.Path[0].Value
|
||||
} else {
|
||||
currentPath += "." + item.Path[0].Value
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all paths, including nested ones
|
||||
paths := []string{currentPath}
|
||||
if item.Expand != nil {
|
||||
for _, subItem := range item.Expand.ExpandItems {
|
||||
subPaths, err := collectExpandPaths(subItem, currentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, subPaths...)
|
||||
}
|
||||
}
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
// validateExpandItem checks if an expand item contains unsupported options.
|
||||
func validateExpandItem(item *godata.ExpandItem) error {
|
||||
if item.Filter != nil || item.At != nil || item.Search != nil ||
|
||||
item.OrderBy != nil || item.Skip != nil || item.Top != nil ||
|
||||
item.Select != nil || item.Compute != nil || item.Levels != 0 {
|
||||
return godata.NotImplementedError("options for $expand not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSelectValues extracts the values of the $select query parameter and
|
||||
// returns them in a []string, rejects any $select value that consists of more
|
||||
// than just a single path segment
|
||||
func GetSelectValues(req *godata.GoDataQuery) ([]string, error) {
|
||||
if req == nil || req.Select == nil {
|
||||
return []string{}, nil
|
||||
}
|
||||
sel := make([]string, 0, len(req.Select.SelectItems))
|
||||
for _, item := range req.Select.SelectItems {
|
||||
if len(item.Segments) > 1 {
|
||||
return []string{}, godata.NotImplementedError("multiple segments in $select not supported")
|
||||
}
|
||||
sel = append(sel, item.Segments[0].Value)
|
||||
}
|
||||
return sel, nil
|
||||
}
|
||||
|
||||
// GetSearchValues extracts the value of the $search query parameter and returns
|
||||
// it as a string. Rejects any search query that is more than just a simple string
|
||||
func GetSearchValues(req *godata.GoDataQuery) (string, error) {
|
||||
if req == nil || req.Search == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Only allow simple search queries for now
|
||||
if len(req.Search.Tree.Children) != 0 {
|
||||
return "", godata.NotImplementedError("complex search queries are not supported")
|
||||
}
|
||||
|
||||
searchValue := strings.Trim(req.Search.Tree.Token.Value, "\"")
|
||||
return searchValue, nil
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package odata
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CiscoM31/godata"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetExpandValues(t *testing.T) {
|
||||
t.Run("NilRequest", func(t *testing.T) {
|
||||
result, err := GetExpandValues(nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("EmptyExpand", func(t *testing.T) {
|
||||
req := &godata.GoDataQuery{}
|
||||
result, err := GetExpandValues(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("SinglePathSegment", func(t *testing.T) {
|
||||
req := &godata.GoDataQuery{
|
||||
Expand: &godata.GoDataExpandQuery{
|
||||
ExpandItems: []*godata.ExpandItem{
|
||||
{Path: []*godata.Token{{Value: "orders"}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := GetExpandValues(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"orders"}, result)
|
||||
})
|
||||
|
||||
t.Run("MultiplePathSegments", func(t *testing.T) {
|
||||
req := &godata.GoDataQuery{
|
||||
Expand: &godata.GoDataExpandQuery{
|
||||
ExpandItems: []*godata.ExpandItem{
|
||||
{Path: []*godata.Token{{Value: "orders"}, {Value: "details"}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := GetExpandValues(req)
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("NestedExpand", func(t *testing.T) {
|
||||
req := &godata.GoDataQuery{
|
||||
Expand: &godata.GoDataExpandQuery{
|
||||
ExpandItems: []*godata.ExpandItem{
|
||||
{
|
||||
Path: []*godata.Token{{Value: "items"}},
|
||||
Expand: &godata.GoDataExpandQuery{
|
||||
ExpandItems: []*godata.ExpandItem{
|
||||
{
|
||||
Path: []*godata.Token{{Value: "subitem"}},
|
||||
Expand: &godata.GoDataExpandQuery{
|
||||
ExpandItems: []*godata.ExpandItem{
|
||||
{Path: []*godata.Token{{Value: "subsubitems"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := GetExpandValues(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Subset(t, result, []string{"items", "items.subitem", "items.subitem.subsubitems"}, "must contain all levels of expansion")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSelectValues(t *testing.T) {
|
||||
t.Run("NilRequest", func(t *testing.T) {
|
||||
result, err := GetSelectValues(nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("EmptySelect", func(t *testing.T) {
|
||||
req := &godata.GoDataQuery{}
|
||||
result, err := GetSelectValues(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("SinglePathSegment", func(t *testing.T) {
|
||||
req := &godata.GoDataQuery{
|
||||
Select: &godata.GoDataSelectQuery{
|
||||
SelectItems: []*godata.SelectItem{
|
||||
{Segments: []*godata.Token{{Value: "name"}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := GetSelectValues(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"name"}, result)
|
||||
})
|
||||
|
||||
t.Run("MultiplePathSegments", func(t *testing.T) {
|
||||
req := &godata.GoDataQuery{
|
||||
Select: &godata.GoDataSelectQuery{
|
||||
SelectItems: []*godata.SelectItem{
|
||||
{Segments: []*godata.Token{{Value: "name"}, {Value: "first"}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := GetSelectValues(req)
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSearchValues(t *testing.T) {
|
||||
t.Run("NilRequest", func(t *testing.T) {
|
||||
result, err := GetSearchValues(nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("EmptySearch", func(t *testing.T) {
|
||||
req := &godata.GoDataQuery{}
|
||||
result, err := GetSearchValues(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("SimpleSearch", func(t *testing.T) {
|
||||
req := &godata.GoDataQuery{
|
||||
Search: &godata.GoDataSearchQuery{
|
||||
Tree: &godata.ParseNode{
|
||||
Token: &godata.Token{Value: "test"},
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := GetSearchValues(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test", result)
|
||||
})
|
||||
|
||||
t.Run("ComplexSearch", func(t *testing.T) {
|
||||
req := &godata.GoDataQuery{
|
||||
Search: &godata.GoDataSearchQuery{
|
||||
Tree: &godata.ParseNode{
|
||||
Children: []*godata.ParseNode{
|
||||
{Token: &godata.Token{Value: "test"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := GetSearchValues(req)
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
stdhttp "net/http"
|
||||
|
||||
@@ -10,7 +8,8 @@ import (
|
||||
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/stream"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
revaMetadata "github.com/opencloud-eu/reva/v2/pkg/storage/utils/metadata"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/metadata"
|
||||
"github.com/pkg/errors"
|
||||
"go-micro.dev/v4"
|
||||
"go-micro.dev/v4/events"
|
||||
|
||||
@@ -21,7 +20,6 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/registry"
|
||||
"github.com/opencloud-eu/opencloud/pkg/service/grpc"
|
||||
"github.com/opencloud-eu/opencloud/pkg/service/http"
|
||||
"github.com/opencloud-eu/opencloud/pkg/storage/metadata"
|
||||
"github.com/opencloud-eu/opencloud/pkg/version"
|
||||
ehsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/eventhistory/v0"
|
||||
searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0"
|
||||
@@ -61,7 +59,7 @@ func Server(opts ...Option) (http.Service, error) {
|
||||
options.Logger.Error().
|
||||
Err(err).
|
||||
Msg("Error initializing events publisher")
|
||||
return http.Service{}, fmt.Errorf("could not initialize events publisher: %w", err)
|
||||
return http.Service{}, errors.Wrap(err, "could not initialize events publisher")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +106,7 @@ func Server(opts ...Option) (http.Service, error) {
|
||||
pool.WithTracerProvider(options.TraceProvider),
|
||||
)...)
|
||||
if err != nil {
|
||||
return http.Service{}, fmt.Errorf("could not initialize gateway selector: %w", err)
|
||||
return http.Service{}, errors.Wrap(err, "could not initialize gateway selector")
|
||||
}
|
||||
} else {
|
||||
middlewares = append(middlewares, graphMiddleware.Token(options.Config.HTTP.APIToken))
|
||||
@@ -131,38 +129,21 @@ func Server(opts ...Option) (http.Service, error) {
|
||||
|
||||
hClient := ehsvc.NewEventHistoryService("eu.opencloud.api.eventhistory", grpcClient)
|
||||
|
||||
var userProfilePhotoService svc.UsersUserProfilePhotoProvider
|
||||
{
|
||||
photoStorage, err := revaMetadata.NewCS3Storage(
|
||||
options.Config.Metadata.GatewayAddress,
|
||||
options.Config.Metadata.StorageAddress,
|
||||
options.Config.Metadata.SystemUserID,
|
||||
options.Config.Metadata.SystemUserIDP,
|
||||
options.Config.Metadata.SystemUserAPIKey,
|
||||
)
|
||||
if err != nil {
|
||||
return http.Service{}, fmt.Errorf("could not initialize reva metadata storage: %w", err)
|
||||
}
|
||||
|
||||
photoStorage, err = metadata.NewLazyStorage(photoStorage)
|
||||
if err != nil {
|
||||
return http.Service{}, fmt.Errorf("could not initialize lazy metadata storage: %w", err)
|
||||
}
|
||||
|
||||
if err := photoStorage.Init(context.Background(), "f2bdd61a-da7c-49fc-8203-0558109d1b4f"); err != nil {
|
||||
return http.Service{}, fmt.Errorf("could not initialize metadata storage: %w", err)
|
||||
}
|
||||
|
||||
userProfilePhotoService, err = svc.NewUsersUserProfilePhotoService(photoStorage)
|
||||
if err != nil {
|
||||
return http.Service{}, fmt.Errorf("could not initialize user profile photo service: %w", err)
|
||||
}
|
||||
storage, err := metadata.NewCS3Storage(
|
||||
options.Config.Metadata.GatewayAddress,
|
||||
options.Config.Metadata.StorageAddress,
|
||||
options.Config.Metadata.SystemUserID,
|
||||
options.Config.Metadata.SystemUserIDP,
|
||||
options.Config.Metadata.SystemUserAPIKey,
|
||||
)
|
||||
if err != nil {
|
||||
return http.Service{}, fmt.Errorf("could not initialize metadata storage: %w", err)
|
||||
}
|
||||
|
||||
var handle svc.Service
|
||||
handle, err = svc.NewService(
|
||||
svc.Context(options.Context),
|
||||
svc.UserProfilePhotoService(userProfilePhotoService),
|
||||
svc.MetadataStorage(storage),
|
||||
svc.Logger(options.Logger),
|
||||
svc.Config(options.Config),
|
||||
svc.Middleware(middlewares...),
|
||||
@@ -179,11 +160,11 @@ func Server(opts ...Option) (http.Service, error) {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return http.Service{}, fmt.Errorf("could not initialize graph service: %w", err)
|
||||
return http.Service{}, errors.New("could not initialize graph service")
|
||||
}
|
||||
|
||||
if err := micro.RegisterHandler(service.Server(), handle); err != nil {
|
||||
return http.Service{}, fmt.Errorf("could not register graph service handler: %w", err)
|
||||
return http.Service{}, err
|
||||
}
|
||||
|
||||
return service, nil
|
||||
|
||||
@@ -31,7 +31,6 @@ import (
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/l10n"
|
||||
l10n_pkg "github.com/opencloud-eu/opencloud/services/graph/pkg/l10n"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/odata"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/conversions"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
@@ -46,15 +45,14 @@ const (
|
||||
invalidIdMsg = "invalid driveID or itemID"
|
||||
parseDriveIDErrMsg = "could not parse driveID"
|
||||
federatedRolesODataFilter = "@libre.graph.permissions.roles.allowedValues/rolePermissions/any(p:contains(p/condition, '@Subject.UserType==\"Federated\"'))"
|
||||
noLinksODataFilter = "grantedToV2 ne ''"
|
||||
)
|
||||
|
||||
// DriveItemPermissionsProvider contains the methods related to handling permissions on drive items
|
||||
type DriveItemPermissionsProvider interface {
|
||||
Invite(ctx context.Context, resourceId *storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error)
|
||||
SpaceRootInvite(ctx context.Context, driveID *storageprovider.ResourceId, invite libregraph.DriveItemInvite) (libregraph.Permission, error)
|
||||
ListPermissions(ctx context.Context, itemID *storageprovider.ResourceId, queryOptions ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error)
|
||||
ListSpaceRootPermissions(ctx context.Context, driveID *storageprovider.ResourceId, queryOptions ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error)
|
||||
ListPermissions(ctx context.Context, itemID *storageprovider.ResourceId, listFederatedRoles bool, selectedAttrs map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error)
|
||||
ListSpaceRootPermissions(ctx context.Context, driveID *storageprovider.ResourceId, selectedAttrs map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error)
|
||||
DeletePermission(ctx context.Context, itemID *storageprovider.ResourceId, permissionID string) error
|
||||
DeleteSpaceRootPermission(ctx context.Context, driveID *storageprovider.ResourceId, permissionID string) error
|
||||
UpdatePermission(ctx context.Context, itemID *storageprovider.ResourceId, permissionID string, newPermission libregraph.Permission) (libregraph.Permission, error)
|
||||
@@ -80,14 +78,6 @@ const (
|
||||
OCM
|
||||
)
|
||||
|
||||
type ListPermissionsQueryOptions struct {
|
||||
Count bool
|
||||
NoValues bool
|
||||
NoLinkPermissions bool
|
||||
FilterFederatedRoles bool
|
||||
SelectedAttrs []string
|
||||
}
|
||||
|
||||
// NewDriveItemPermissionsService creates a new DriveItemPermissionsService
|
||||
func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], identityCache identity.IdentityCache, config *config.Config) (DriveItemPermissionsService, error) {
|
||||
return DriveItemPermissionsService{
|
||||
@@ -354,7 +344,7 @@ func (s DriveItemPermissionsService) SpaceRootInvite(ctx context.Context, driveI
|
||||
}
|
||||
|
||||
// ListPermissions lists the permissions of a driveItem
|
||||
func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID *storageprovider.ResourceId, queryOptions ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) {
|
||||
func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID *storageprovider.ResourceId, listFederatedRoles bool, selectedAttrs map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error) {
|
||||
collectionOfPermissions := libregraph.CollectionOfPermissionsWithAllowedValues{}
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
@@ -377,17 +367,17 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID
|
||||
|
||||
collectionOfPermissions = libregraph.CollectionOfPermissionsWithAllowedValues{}
|
||||
|
||||
if len(queryOptions.SelectedAttrs) == 0 || slices.Contains(queryOptions.SelectedAttrs, "@libre.graph.permissions.actions.allowedValues") {
|
||||
if _, ok := selectedAttrs["@libre.graph.permissions.actions.allowedValues"]; ok || len(selectedAttrs) == 0 {
|
||||
collectionOfPermissions.LibreGraphPermissionsActionsAllowedValues = allowedActions
|
||||
}
|
||||
|
||||
if len(queryOptions.SelectedAttrs) == 0 || slices.Contains(queryOptions.SelectedAttrs, "@libre.graph.permissions.roles.allowedValues") {
|
||||
if _, ok := selectedAttrs["@libre.graph.permissions.roles.allowedValues"]; ok || len(selectedAttrs) == 0 {
|
||||
collectionOfPermissions.LibreGraphPermissionsRolesAllowedValues = conversions.ToValueSlice(
|
||||
unifiedrole.GetRolesByPermissions(
|
||||
unifiedrole.GetRoles(unifiedrole.RoleFilterIDs(s.config.UnifiedRoles.AvailableRoles...)),
|
||||
allowedActions,
|
||||
condition,
|
||||
queryOptions.FilterFederatedRoles,
|
||||
listFederatedRoles,
|
||||
false,
|
||||
),
|
||||
)
|
||||
@@ -399,7 +389,7 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID
|
||||
collectionOfPermissions.LibreGraphPermissionsRolesAllowedValues[i] = definition
|
||||
}
|
||||
|
||||
if len(queryOptions.SelectedAttrs) > 0 {
|
||||
if len(selectedAttrs) > 0 {
|
||||
// no need to fetch shares, we are only interested allowedActions and/or allowedRoles
|
||||
return collectionOfPermissions, nil
|
||||
}
|
||||
@@ -412,11 +402,8 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID
|
||||
}
|
||||
driveItems[storagespace.FormatResourceID(statResponse.GetInfo().GetId())] = *item
|
||||
|
||||
var permissionsCount int
|
||||
|
||||
if IsSpaceRoot(statResponse.GetInfo().GetId()) {
|
||||
var permissions []libregraph.Permission
|
||||
permissions, permissionsCount, err = s.getSpaceRootPermissions(ctx, statResponse.GetInfo().GetSpace().GetId(), queryOptions.NoValues)
|
||||
permissions, err := s.getSpaceRootPermissions(ctx, statResponse.GetInfo().GetSpace().GetId())
|
||||
if err != nil {
|
||||
return collectionOfPermissions, err
|
||||
}
|
||||
@@ -441,33 +428,23 @@ func (s DriveItemPermissionsService) ListPermissions(ctx context.Context, itemID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !queryOptions.NoLinkPermissions {
|
||||
// finally get public shares, which are possible for spaceroots and "normal" resources
|
||||
driveItems, err = s.listPublicShares(ctx, []*link.ListPublicSharesRequest_Filter{
|
||||
publicshare.ResourceIDFilter(itemID),
|
||||
}, driveItems)
|
||||
if err != nil {
|
||||
return collectionOfPermissions, err
|
||||
}
|
||||
// finally get public shares, which are possible for spaceroots and "normal" resources
|
||||
driveItems, err = s.listPublicShares(ctx, []*link.ListPublicSharesRequest_Filter{
|
||||
publicshare.ResourceIDFilter(itemID),
|
||||
}, driveItems)
|
||||
if err != nil {
|
||||
return collectionOfPermissions, err
|
||||
}
|
||||
|
||||
for _, driveItem := range driveItems {
|
||||
permissionsCount += len(driveItem.Permissions)
|
||||
if !queryOptions.NoValues {
|
||||
collectionOfPermissions.Value = append(collectionOfPermissions.Value, driveItem.Permissions...)
|
||||
}
|
||||
}
|
||||
|
||||
if queryOptions.Count {
|
||||
collectionOfPermissions.SetOdataCount(int32(permissionsCount))
|
||||
collectionOfPermissions.Value = append(collectionOfPermissions.Value, driveItem.Permissions...)
|
||||
}
|
||||
|
||||
return collectionOfPermissions, nil
|
||||
}
|
||||
|
||||
// ListSpaceRootPermissions handles ListPermissions request on project spaces
|
||||
func (s DriveItemPermissionsService) ListSpaceRootPermissions(ctx context.Context, driveID *storageprovider.ResourceId, queryOptions ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) {
|
||||
func (s DriveItemPermissionsService) ListSpaceRootPermissions(ctx context.Context, driveID *storageprovider.ResourceId, selectedAttrs map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error) {
|
||||
collectionOfPermissions := libregraph.CollectionOfPermissionsWithAllowedValues{}
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
@@ -485,7 +462,7 @@ func (s DriveItemPermissionsService) ListSpaceRootPermissions(ctx context.Contex
|
||||
}
|
||||
|
||||
rootResourceID := space.GetRoot()
|
||||
return s.ListPermissions(ctx, rootResourceID, queryOptions) // federated roles are not supported for spaces
|
||||
return s.ListPermissions(ctx, rootResourceID, false, selectedAttrs) // federated roles are not supported for spaces
|
||||
}
|
||||
|
||||
// DeletePermission deletes a permission from a drive item
|
||||
@@ -738,17 +715,23 @@ func (api DriveItemPermissionsApi) ListPermissions(w http.ResponseWriter, r *htt
|
||||
return
|
||||
}
|
||||
|
||||
var queryOptions ListPermissionsQueryOptions
|
||||
queryOptions, err = api.getListPermissionsQueryOptions(odataReq)
|
||||
var listFederatedRoles bool
|
||||
if odataReq.Query.Filter != nil {
|
||||
if odataReq.Query.Filter.RawValue == federatedRolesODataFilter {
|
||||
listFederatedRoles = true
|
||||
}
|
||||
}
|
||||
|
||||
selectRoles, err := api.listPermissionsQuerySelectValues(odataReq.Query)
|
||||
if err != nil {
|
||||
api.logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("Error parsing ListPermissionRequest query options")
|
||||
api.logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("Error parsing ListPermissionRequest: query error")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
permissions, err := api.driveItemPermissionsService.ListPermissions(ctx, itemID, queryOptions)
|
||||
permissions, err := api.driveItemPermissionsService.ListPermissions(ctx, itemID, listFederatedRoles, selectRoles)
|
||||
if err != nil {
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
@@ -789,16 +772,15 @@ func (api DriveItemPermissionsApi) ListSpaceRootPermissions(w http.ResponseWrite
|
||||
return
|
||||
}
|
||||
|
||||
var queryOptions ListPermissionsQueryOptions
|
||||
queryOptions, err = api.getListPermissionsQueryOptions(odataReq)
|
||||
selected, err := api.listPermissionsQuerySelectValues(odataReq.Query)
|
||||
if err != nil {
|
||||
api.logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("Error parsing ListPermissionRequest query options")
|
||||
api.logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("Error parsing ListPermissionRequest: query error")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
permissions, err := api.driveItemPermissionsService.ListSpaceRootPermissions(ctx, &driveID, queryOptions)
|
||||
permissions, err := api.driveItemPermissionsService.ListSpaceRootPermissions(ctx, &driveID, selected)
|
||||
|
||||
if err != nil {
|
||||
errorcode.RenderError(w, r, err)
|
||||
@@ -823,42 +805,6 @@ func (api DriveItemPermissionsApi) ListSpaceRootPermissions(w http.ResponseWrite
|
||||
render.JSON(w, r, permissions)
|
||||
}
|
||||
|
||||
func (api DriveItemPermissionsApi) getListPermissionsQueryOptions(odataReq *godata.GoDataRequest) (ListPermissionsQueryOptions, error) {
|
||||
queryOptions := ListPermissionsQueryOptions{}
|
||||
if odataReq.Query.Filter != nil {
|
||||
switch odataReq.Query.Filter.RawValue {
|
||||
case federatedRolesODataFilter:
|
||||
queryOptions.FilterFederatedRoles = true
|
||||
case noLinksODataFilter:
|
||||
queryOptions.NoLinkPermissions = true
|
||||
default:
|
||||
return ListPermissionsQueryOptions{}, errorcode.New(errorcode.InvalidRequest, "invalid filter value")
|
||||
}
|
||||
}
|
||||
|
||||
selectAttrs, err := odata.GetSelectValues(odataReq.Query)
|
||||
if err != nil {
|
||||
return ListPermissionsQueryOptions{}, err
|
||||
}
|
||||
|
||||
queryOptions.SelectedAttrs = selectAttrs
|
||||
if odataReq.Query.Count != nil {
|
||||
queryOptions.Count = bool(*odataReq.Query.Count)
|
||||
}
|
||||
if odataReq.Query.Top != nil {
|
||||
top := int(*odataReq.Query.Top)
|
||||
switch {
|
||||
case top != 0:
|
||||
return ListPermissionsQueryOptions{}, err
|
||||
case top == 0 && !queryOptions.Count:
|
||||
return ListPermissionsQueryOptions{}, err
|
||||
default:
|
||||
queryOptions.NoValues = true
|
||||
}
|
||||
}
|
||||
return queryOptions, nil
|
||||
}
|
||||
|
||||
// DeletePermission handles DeletePermission requests
|
||||
func (api DriveItemPermissionsApi) DeletePermission(w http.ResponseWriter, r *http.Request) {
|
||||
_, itemID, err := GetDriveAndItemIDParam(r, &api.logger)
|
||||
@@ -990,3 +936,19 @@ func (api DriveItemPermissionsApi) UpdateSpaceRootPermission(w http.ResponseWrit
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, &updatedPermission)
|
||||
}
|
||||
|
||||
func (api DriveItemPermissionsApi) listPermissionsQuerySelectValues(odataQuery *godata.GoDataQuery) (map[string]struct{}, error) {
|
||||
selectedAttrs := map[string]struct{}{}
|
||||
if odataQuery.Select != nil {
|
||||
for _, item := range odataQuery.Select.SelectItems {
|
||||
// for now we only support a limitted set of $select attributes
|
||||
if item.Segments[0].Value == "@libre.graph.permissions.roles.allowedValues" || item.Segments[0].Value == "@libre.graph.permissions.actions.allowedValues" {
|
||||
selectedAttrs[item.Segments[0].Value] = struct{}{}
|
||||
} else {
|
||||
api.logger.Debug().Msg("Error parsing ListPermissionRequest: unsupported select item")
|
||||
return selectedAttrs, errorcode.New(errorcode.InvalidRequest, "unsupported select item")
|
||||
}
|
||||
}
|
||||
}
|
||||
return selectedAttrs, nil
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
|
||||
gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil)
|
||||
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil)
|
||||
permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, svc.ListPermissionsQueryOptions{})
|
||||
permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, false, map[string]struct{}{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero())
|
||||
Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero())
|
||||
@@ -433,7 +433,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil)
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
|
||||
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil)
|
||||
permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, svc.ListPermissionsQueryOptions{})
|
||||
permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, false, map[string]struct{}{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero())
|
||||
Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero())
|
||||
@@ -472,7 +472,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil)
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
|
||||
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil)
|
||||
permissions, err := service.ListPermissions(context.Background(), itemID, svc.ListPermissionsQueryOptions{})
|
||||
permissions, err := service.ListPermissions(context.Background(), itemID, false, map[string]struct{}{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero())
|
||||
Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero())
|
||||
@@ -508,126 +508,13 @@ var _ = Describe("DriveItemPermissionsService", func() {
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil)
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
|
||||
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil)
|
||||
permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, svc.ListPermissionsQueryOptions{})
|
||||
permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, false, map[string]struct{}{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero())
|
||||
Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero())
|
||||
Expect(len(permissions.Value)).To(Equal(1))
|
||||
Expect(permissions.Value[0].GetLibreGraphPermissionsActions()[0]).To(Equal("none"))
|
||||
})
|
||||
It("Does not list public shares when requested so", func() {
|
||||
opt := svc.ListPermissionsQueryOptions{
|
||||
NoLinkPermissions: true,
|
||||
}
|
||||
gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil)
|
||||
permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, opt)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero())
|
||||
Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero())
|
||||
})
|
||||
It("Does not return permissions when the NoValues option is set", func() {
|
||||
opt := svc.ListPermissionsQueryOptions{
|
||||
NoValues: true,
|
||||
}
|
||||
listSharesResponse.Shares = []*collaboration.Share{
|
||||
{
|
||||
Id: &collaboration.ShareId{OpaqueId: "1"},
|
||||
Permissions: &collaboration.SharePermissions{
|
||||
Permissions: roleconversions.NewViewerRole().CS3ResourcePermissions(),
|
||||
},
|
||||
ResourceId: &provider.ResourceId{
|
||||
StorageId: "1",
|
||||
SpaceId: "2",
|
||||
OpaqueId: "3",
|
||||
},
|
||||
Grantee: &provider.Grantee{
|
||||
Type: provider.GranteeType_GRANTEE_TYPE_USER,
|
||||
Id: &provider.Grantee_UserId{
|
||||
UserId: &userpb.UserId{
|
||||
OpaqueId: "user-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
listPublicSharesResponse.Share = []*link.PublicShare{
|
||||
{
|
||||
Id: &link.PublicShareId{
|
||||
OpaqueId: "public-share-id",
|
||||
},
|
||||
Token: "public-share-token",
|
||||
// the link shares the same resource id
|
||||
ResourceId: &provider.ResourceId{
|
||||
StorageId: "1",
|
||||
SpaceId: "2",
|
||||
OpaqueId: "3",
|
||||
},
|
||||
Permissions: &link.PublicSharePermissions{Permissions: roleconversions.NewViewerRole().CS3ResourcePermissions()},
|
||||
},
|
||||
}
|
||||
gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil)
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
|
||||
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil)
|
||||
permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, opt)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero())
|
||||
Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero())
|
||||
Expect(len(permissions.Value)).To(BeZero())
|
||||
})
|
||||
It("Returns a count when the Count option is set", func() {
|
||||
opt := svc.ListPermissionsQueryOptions{
|
||||
Count: true,
|
||||
}
|
||||
listSharesResponse.Shares = []*collaboration.Share{
|
||||
{
|
||||
Id: &collaboration.ShareId{OpaqueId: "1"},
|
||||
Permissions: &collaboration.SharePermissions{
|
||||
Permissions: roleconversions.NewViewerRole().CS3ResourcePermissions(),
|
||||
},
|
||||
ResourceId: &provider.ResourceId{
|
||||
StorageId: "1",
|
||||
SpaceId: "2",
|
||||
OpaqueId: "3",
|
||||
},
|
||||
Grantee: &provider.Grantee{
|
||||
Type: provider.GranteeType_GRANTEE_TYPE_USER,
|
||||
Id: &provider.Grantee_UserId{
|
||||
UserId: &userpb.UserId{
|
||||
OpaqueId: "user-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
listPublicSharesResponse.Share = []*link.PublicShare{
|
||||
{
|
||||
Id: &link.PublicShareId{
|
||||
OpaqueId: "public-share-id",
|
||||
},
|
||||
Token: "public-share-token",
|
||||
// the link shares the same resource id
|
||||
ResourceId: &provider.ResourceId{
|
||||
StorageId: "1",
|
||||
SpaceId: "2",
|
||||
OpaqueId: "3",
|
||||
},
|
||||
Permissions: &link.PublicSharePermissions{Permissions: roleconversions.NewViewerRole().CS3ResourcePermissions()},
|
||||
},
|
||||
}
|
||||
gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
|
||||
gatewayClient.On("ListShares", mock.Anything, mock.Anything).Return(listSharesResponse, nil)
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
|
||||
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil)
|
||||
permissions, err := driveItemPermissionsService.ListPermissions(context.Background(), itemID, opt)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero())
|
||||
Expect(len(permissions.LibreGraphPermissionsRolesAllowedValues)).ToNot(BeZero())
|
||||
count := int(permissions.GetOdataCount())
|
||||
Expect(count).To(Equal(2)) // 1 share + 1 public share
|
||||
Expect(len(permissions.Value)).To(Equal(count))
|
||||
})
|
||||
})
|
||||
Describe("ListSpaceRootPermissions", func() {
|
||||
var (
|
||||
@@ -668,7 +555,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
|
||||
gatewayClient.On("ListPublicShares", mock.Anything, mock.Anything).Return(listPublicSharesResponse, nil)
|
||||
statResponse.Info.Id = listSpacesResponse.StorageSpaces[0].Root
|
||||
gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(statResponse, nil)
|
||||
permissions, err := driveItemPermissionsService.ListSpaceRootPermissions(context.Background(), driveId, svc.ListPermissionsQueryOptions{})
|
||||
permissions, err := driveItemPermissionsService.ListSpaceRootPermissions(context.Background(), driveId, map[string]struct{}{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(permissions.LibreGraphPermissionsActionsAllowedValues)).ToNot(BeZero())
|
||||
})
|
||||
@@ -1381,7 +1268,8 @@ var _ = Describe("DriveItemPermissionsApi", func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
mockProvider.On("ListPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(func(ctx context.Context, itemid *provider.ResourceId, opt svc.ListPermissionsQueryOptions) (libregraph.CollectionOfPermissionsWithAllowedValues, error) {
|
||||
Return(func(ctx context.Context, itemid *provider.ResourceId, listFederatedRoles bool, selected map[string]struct{}) (libregraph.CollectionOfPermissionsWithAllowedValues, error) {
|
||||
Expect(listFederatedRoles).To(Equal(false))
|
||||
Expect(storagespace.FormatResourceID(itemid)).To(Equal("1$2!3"))
|
||||
return libregraph.CollectionOfPermissionsWithAllowedValues{}, nil
|
||||
}).Once()
|
||||
|
||||
@@ -30,6 +30,9 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
// profilePhotoSpaceID is the space ID for the profile photo
|
||||
profilePhotoSpaceID = "f2bdd61a-da7c-49fc-8203-0558109d1b4f"
|
||||
|
||||
// ErrNoBytes is returned when no bytes are found
|
||||
ErrNoBytes = errors.New("no bytes")
|
||||
|
||||
@@ -47,6 +50,10 @@ type UsersUserProfilePhotoService struct {
|
||||
|
||||
// NewUsersUserProfilePhotoService creates a new UsersUserProfilePhotoService
|
||||
func NewUsersUserProfilePhotoService(storage metadata.Storage) (UsersUserProfilePhotoService, error) {
|
||||
if err := storage.Init(context.Background(), profilePhotoSpaceID); err != nil {
|
||||
return UsersUserProfilePhotoService{}, err
|
||||
}
|
||||
|
||||
return UsersUserProfilePhotoService{
|
||||
storage: storage,
|
||||
}, nil
|
||||
|
||||
@@ -19,7 +19,10 @@ import (
|
||||
)
|
||||
|
||||
func TestNewUsersUserProfilePhotoService(t *testing.T) {
|
||||
service, err := svc.NewUsersUserProfilePhotoService(mocks.NewStorage(t))
|
||||
storage := mocks.NewStorage(t)
|
||||
storage.EXPECT().Init(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, id string) error { return nil })
|
||||
|
||||
service, err := svc.NewUsersUserProfilePhotoService(storage)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("UpdatePhoto", func(t *testing.T) {
|
||||
|
||||
@@ -71,9 +71,13 @@ var _ = Describe("Applications", func() {
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
cfg.Application.ID = "some-application-ID"
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
|
||||
@@ -81,9 +81,13 @@ var _ = Describe("AppRoleAssignments", func() {
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
cfg.Application.ID = "some-application-ID"
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
|
||||
@@ -49,20 +49,19 @@ type BaseGraphService struct {
|
||||
availableRoles []*libregraph.UnifiedRoleDefinition
|
||||
}
|
||||
|
||||
func (g BaseGraphService) getSpaceRootPermissions(ctx context.Context, spaceID *storageprovider.StorageSpaceId, countOnly bool) ([]libregraph.Permission, int, error) {
|
||||
func (g BaseGraphService) getSpaceRootPermissions(ctx context.Context, spaceID *storageprovider.StorageSpaceId) ([]libregraph.Permission, error) {
|
||||
gatewayClient, err := g.gatewaySelector.Next()
|
||||
|
||||
if err != nil {
|
||||
g.logger.Debug().Err(err).Msg("selecting gatewaySelector failed")
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
space, err := utils.GetSpace(ctx, spaceID.GetOpaqueId(), gatewayClient)
|
||||
if err != nil {
|
||||
return nil, 0, errorcode.FromUtilsStatusCodeError(err)
|
||||
return nil, errorcode.FromUtilsStatusCodeError(err)
|
||||
}
|
||||
|
||||
perm, count := g.cs3SpacePermissionsToLibreGraph(ctx, space, countOnly, APIVersion_1_Beta_1)
|
||||
return perm, count, nil
|
||||
return g.cs3SpacePermissionsToLibreGraph(ctx, space, APIVersion_1_Beta_1), nil
|
||||
}
|
||||
|
||||
func (g BaseGraphService) getDriveItem(ctx context.Context, ref *storageprovider.Reference) (*libregraph.DriveItem, error) {
|
||||
@@ -100,9 +99,9 @@ func (g BaseGraphService) CS3ReceivedOCMSharesToDriveItems(ctx context.Context,
|
||||
return cs3ReceivedOCMSharesToDriveItems(ctx, g.logger, gatewayClient, g.identityCache, receivedShares, g.availableRoles)
|
||||
}
|
||||
|
||||
func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, countOnly bool, apiVersion APIVersion) ([]libregraph.Permission, int) {
|
||||
func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, space *storageprovider.StorageSpace, apiVersion APIVersion) []libregraph.Permission {
|
||||
if space.Opaque == nil {
|
||||
return nil, 0
|
||||
return nil
|
||||
}
|
||||
logger := g.logger.SubloggerWithRequestID(ctx)
|
||||
|
||||
@@ -119,12 +118,7 @@ func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, s
|
||||
}
|
||||
}
|
||||
if len(permissionsMap) == 0 {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
if countOnly {
|
||||
// If we only need the count, we can return early
|
||||
return nil, len(permissionsMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
var permissionsExpirations map[string]*types.Timestamp
|
||||
@@ -225,7 +219,7 @@ func (g BaseGraphService) cs3SpacePermissionsToLibreGraph(ctx context.Context, s
|
||||
|
||||
permissions = append(permissions, p)
|
||||
}
|
||||
return permissions, len(permissions)
|
||||
return permissions
|
||||
}
|
||||
|
||||
func (g BaseGraphService) libreGraphPermissionFromCS3PublicShare(createdLink *link.PublicShare) (*libregraph.Permission, error) {
|
||||
@@ -1074,7 +1068,7 @@ func (g BaseGraphService) getPermissionByID(ctx context.Context, permissionID st
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
perms, _, err := g.getSpaceRootPermissions(ctx, resourceInfo.GetSpace().GetId(), false)
|
||||
perms, err := g.getSpaceRootPermissions(ctx, resourceInfo.GetSpace().GetId())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -85,9 +85,13 @@ var _ = Describe("Driveitems", func() {
|
||||
cfg.Commons = &shared.Commons{}
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -20,10 +19,10 @@ import (
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/go-chi/render"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
"github.com/pkg/errors"
|
||||
merrors "go-micro.dev/v4/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -33,7 +32,6 @@ import (
|
||||
v0 "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/settings/v0"
|
||||
settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/odata"
|
||||
settingsServiceExt "github.com/opencloud-eu/opencloud/services/settings/pkg/store/defaults"
|
||||
)
|
||||
|
||||
@@ -170,60 +168,31 @@ func (g Graph) GetAllDrivesV1Beta1(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizePath(path string, apiVersion APIVersion) string {
|
||||
switch apiVersion {
|
||||
case APIVersion_1:
|
||||
return strings.TrimPrefix(path, "/graph/v1.0/")
|
||||
case APIVersion_1_Beta_1:
|
||||
return strings.TrimPrefix(path, "/graph/v1beta1/")
|
||||
default:
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
// parseDriveRequest parses the odata request and returns the parsed request and a boolean indicating if the request should expand root driveItems.
|
||||
func parseDriveRequest(r *http.Request) (*godata.GoDataRequest, bool, error) {
|
||||
odataReq, err := godata.ParseRequest(r.Context(), sanitizePath(r.URL.Path, APIVersion_1), r.URL.Query())
|
||||
if err != nil {
|
||||
return nil, false, errorcode.New(errorcode.InvalidRequest, err.Error())
|
||||
}
|
||||
exp, err := odata.GetExpandValues(odataReq.Query)
|
||||
if err != nil {
|
||||
return nil, false, errorcode.New(errorcode.InvalidRequest, err.Error())
|
||||
}
|
||||
expandPermissions := slices.Contains(exp, "root.permissions")
|
||||
return odataReq, expandPermissions, nil
|
||||
}
|
||||
|
||||
// getDrives implements the Service interface.
|
||||
func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersion) ([]*libregraph.Drive, error) {
|
||||
logger := g.logger.SubloggerWithRequestID(r.Context())
|
||||
logger.Info().
|
||||
Interface("query", r.URL.Query()).
|
||||
Bool("unrestricted", unrestricted).
|
||||
Msg("calling get drives")
|
||||
sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
|
||||
// Parse the request with odata parser
|
||||
odataReq, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get drives: query error")
|
||||
return nil, errorcode.New(errorcode.InvalidRequest, err.Error())
|
||||
}
|
||||
ctx := r.Context()
|
||||
log := g.logger.SubloggerWithRequestID(ctx).With().Interface("query", r.URL.Query()).Bool("unrestricted", unrestricted).Logger()
|
||||
log.Debug().Msg("calling get drives")
|
||||
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not get drives: error parsing url")
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
|
||||
log = log.With().Str("url", webDavBaseURL.String()).Logger()
|
||||
|
||||
odataReq, expandPermissions, err := parseDriveRequest(r)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drives: error parsing odata request")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filters, err := generateCs3Filters(odataReq)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drives: error parsing filters")
|
||||
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get drives: error parsing filters")
|
||||
return nil, errorcode.New(errorcode.NotSupported, err.Error())
|
||||
}
|
||||
if !unrestricted {
|
||||
user, ok := revactx.ContextGetUser(r.Context())
|
||||
if !ok {
|
||||
log.Debug().Msg("could not create drive: invalid user")
|
||||
logger.Debug().Msg("could not create drive: invalid user")
|
||||
return nil, errorcode.New(errorcode.AccessDenied, "invalid user")
|
||||
}
|
||||
filters = append(filters, &storageprovider.ListStorageSpacesRequest_Filter{
|
||||
@@ -234,32 +203,39 @@ func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersi
|
||||
})
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
logger.Debug().
|
||||
Interface("filters", filters).
|
||||
Bool("unrestricted", unrestricted).
|
||||
Msg("calling list storage spaces on backend")
|
||||
res, err := g.ListStorageSpacesWithFilters(ctx, filters, unrestricted)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Error().Err(err).Msg("could not get drives: transport error")
|
||||
logger.Error().Err(err).Msg("could not get drives: transport error")
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
case res.Status.Code != cs3rpc.Code_CODE_OK:
|
||||
if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
|
||||
// ok, empty return
|
||||
return nil, nil
|
||||
}
|
||||
log.Debug().Str("message", res.GetStatus().GetMessage()).Msg("could not get drives: grpc error")
|
||||
logger.Debug().Str("message", res.GetStatus().GetMessage()).Msg("could not get drives: grpc error")
|
||||
return nil, errorcode.New(errorcode.GeneralException, res.Status.Message)
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, apiVersion, expandPermissions)
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drives: error parsing grpc response")
|
||||
logger.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get drives: error parsing url")
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, apiVersion)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("could not get drives: error parsing grpc response")
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
|
||||
spaces, err = sortSpaces(odataReq, spaces)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drives: error sorting the spaces list according to query")
|
||||
logger.Debug().Err(err).Msg("could not get drives: error sorting the spaces list according to query")
|
||||
return nil, errorcode.New(errorcode.InvalidRequest, err.Error())
|
||||
}
|
||||
|
||||
@@ -269,32 +245,15 @@ func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersi
|
||||
// GetSingleDrive does a lookup of a single space by spaceId
|
||||
func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
log := g.logger.SubloggerWithRequestID(ctx).With().Interface("query", r.URL.Query()).Logger()
|
||||
log.Debug().Msg("calling get drive")
|
||||
logger := g.logger.SubloggerWithRequestID(ctx)
|
||||
logger.Info().Interface("query", r.URL.Query()).Msg("calling get drive")
|
||||
|
||||
rid, err := parseIDParam(r, "driveID")
|
||||
if err != nil {
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
log = log.With().Str("storage", rid.StorageId).Str("space", rid.SpaceId).Str("node", rid.OpaqueId).Logger()
|
||||
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not get drive: error parsing webdav base url")
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
log = log.With().Str("url", webDavBaseURL.String()).Logger()
|
||||
|
||||
_, expandPermissions, err := parseDriveRequest(r)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drives: error parsing odata request")
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
}
|
||||
log := logger.With().Str("storage", rid.StorageId).Str("space", rid.SpaceId).Str("node", rid.OpaqueId).Logger()
|
||||
|
||||
log.Debug().Msg("calling list storage spaces with id filter")
|
||||
|
||||
@@ -322,7 +281,13 @@ func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions)
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get drive: error parsing webdav base url")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
@@ -359,22 +324,14 @@ func (g Graph) canCreateSpace(ctx context.Context, ownPersonalHome bool) bool {
|
||||
|
||||
// CreateDrive creates a storage drive (space).
|
||||
func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
logger := g.logger.SubloggerWithRequestID(r.Context())
|
||||
logger.Info().Msg("calling create drive")
|
||||
|
||||
ctx := r.Context()
|
||||
log := g.logger.SubloggerWithRequestID(ctx).With().Interface("query", r.URL.Query()).Logger()
|
||||
log.Debug().Msg("calling create drive")
|
||||
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not create drive: error parsing webdav base url")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log = log.With().Str("url", webDavBaseURL.String()).Logger()
|
||||
|
||||
us, ok := revactx.ContextGetUser(ctx)
|
||||
if !ok {
|
||||
log.Debug().Msg("could not create drive: invalid user")
|
||||
logger.Debug().Msg("could not create drive: invalid user")
|
||||
errorcode.NotAllowed.Render(w, r, http.StatusUnauthorized, "invalid user")
|
||||
return
|
||||
}
|
||||
@@ -382,7 +339,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO determine if the user tries to create his own personal space and pass that as a boolean
|
||||
canCreateSpace := g.canCreateSpace(ctx, false)
|
||||
if !canCreateSpace {
|
||||
log.Debug().Bool("cancreatespace", canCreateSpace).Msg("could not create drive: insufficient permissions")
|
||||
logger.Debug().Bool("cancreatespace", canCreateSpace).Msg("could not create drive: insufficient permissions")
|
||||
// if the permission is not existing for the user in context we can assume we don't have it. Return 401.
|
||||
errorcode.NotAllowed.Render(w, r, http.StatusForbidden, "insufficient permissions to create a space.")
|
||||
return
|
||||
@@ -390,20 +347,20 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
gatewayClient, err := g.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not select next gateway client")
|
||||
logger.Error().Err(err).Msg("could not select next gateway client")
|
||||
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "could not select next gateway client, aborting")
|
||||
return
|
||||
}
|
||||
|
||||
drive := libregraph.Drive{}
|
||||
if err := StrictJSONUnmarshal(r.Body, &drive); err != nil {
|
||||
log.Debug().Err(err).Interface("body", r.Body).Msg("could not create drive: invalid body schema definition")
|
||||
logger.Debug().Err(err).Interface("body", r.Body).Msg("could not create drive: invalid body schema definition")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid body schema definition")
|
||||
return
|
||||
}
|
||||
spaceName := strings.TrimSpace(drive.Name)
|
||||
if err := validateSpaceName(spaceName); err != nil {
|
||||
log.Debug().Str("name", spaceName).Err(err).Msg("could not create drive: name validation failed")
|
||||
logger.Debug().Str("name", spaceName).Err(err).Msg("could not create drive: name validation failed")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid spacename: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
@@ -416,7 +373,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
case "", _spaceTypeProject:
|
||||
driveType = _spaceTypeProject
|
||||
default:
|
||||
log.Debug().Str("type", driveType).Msg("could not create drive: drives of this type cannot be created via this api")
|
||||
logger.Debug().Str("type", driveType).Msg("could not create drive: drives of this type cannot be created via this api")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "drives of this type cannot be created via this api")
|
||||
return
|
||||
}
|
||||
@@ -441,32 +398,39 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
resp, err := gatewayClient.CreateStorageSpace(ctx, &csr)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not create drive: transport error")
|
||||
logger.Error().Err(err).Msg("could not create drive: transport error")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if resp.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
|
||||
if resp.GetStatus().GetCode() == cs3rpc.Code_CODE_PERMISSION_DENIED {
|
||||
log.Debug().Str("grpcmessage", resp.GetStatus().GetMessage()).Msg("could not create drive: permission denied")
|
||||
logger.Debug().Str("grpcmessage", resp.GetStatus().GetMessage()).Msg("could not create drive: permission denied")
|
||||
errorcode.NotAllowed.Render(w, r, http.StatusForbidden, "permission denied")
|
||||
return
|
||||
}
|
||||
if resp.GetStatus().GetCode() == cs3rpc.Code_CODE_INVALID_ARGUMENT {
|
||||
log.Debug().Str("grpcmessage", resp.GetStatus().GetMessage()).Msg("could not create drive: bad request")
|
||||
logger.Debug().Str("grpcmessage", resp.GetStatus().GetMessage()).Msg("could not create drive: bad request")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, resp.GetStatus().GetMessage())
|
||||
return
|
||||
}
|
||||
log.Debug().Interface("grpcmessage", csr).Str("grpc", resp.GetStatus().GetMessage()).Msg("could not create drive: grpc error")
|
||||
logger.Debug().Interface("grpcmessage", csr).Str("grpc", resp.GetStatus().GetMessage()).Msg("could not create drive: grpc error")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, resp.GetStatus().GetMessage())
|
||||
return
|
||||
}
|
||||
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
logger.Error().Str("url", webDavBaseURL.String()).Err(err).Msg("could not create drive: error parsing webdav base url")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
space := resp.GetStorageSpace()
|
||||
if t := r.URL.Query().Get(TemplateParameter); t != "" && driveType == _spaceTypeProject {
|
||||
loc := l10n.MustGetUserLocale(ctx, us.GetId().GetOpaqueId(), r.Header.Get(HeaderAcceptLanguage), g.valueService)
|
||||
if err := g.applySpaceTemplate(ctx, gatewayClient, space.GetRoot(), t, loc); err != nil {
|
||||
log.Error().Err(err).Msg("could not apply template to space")
|
||||
logger.Error().Err(err).Msg("could not apply template to space")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -474,20 +438,20 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
// refetch the drive to get quota information - should we calculate this ourselves to avoid the extra call?
|
||||
space, err = utils.GetSpace(ctx, space.GetId().GetOpaqueId(), gatewayClient)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not refetch space")
|
||||
logger.Error().Err(err).Msg("could not refetch space")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, []*storageprovider.StorageSpace{space}, APIVersion_1, false)
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, []*storageprovider.StorageSpace{space}, APIVersion_1)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
|
||||
logger.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if len(spaces) == 0 {
|
||||
log.Error().Msg("could not convert space")
|
||||
logger.Error().Msg("could not convert space")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not convert space")
|
||||
return
|
||||
}
|
||||
@@ -498,8 +462,8 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// UpdateDrive updates the properties of a storage drive (space).
|
||||
func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
log := g.logger.SubloggerWithRequestID(r.Context()).With().Interface("query", r.URL.Query()).Logger()
|
||||
log.Debug().Msg("calling update drive")
|
||||
logger := g.logger.SubloggerWithRequestID(r.Context())
|
||||
logger.Info().Msg("calling update drive")
|
||||
|
||||
rid, err := parseIDParam(r, "driveID")
|
||||
if err != nil {
|
||||
@@ -507,20 +471,9 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log = log.With().Str("storage", rid.StorageId).Str("space", rid.SpaceId).Str("node", rid.OpaqueId).Logger()
|
||||
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Interface("url", webDavBaseURL.String()).Msg("could not update drive: error parsing url")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log = log.With().Str("url", webDavBaseURL.String()).Logger()
|
||||
|
||||
drive := libregraph.DriveUpdate{}
|
||||
if err = StrictJSONUnmarshal(r.Body, &drive); err != nil {
|
||||
log.Debug().Err(err).Interface("body", r.Body).Msg("could not update drive, invalid request body")
|
||||
logger.Debug().Err(err).Interface("body", r.Body).Msg("could not update drive, invalid request body")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("invalid request body: error: %v", err.Error()))
|
||||
return
|
||||
}
|
||||
@@ -573,7 +526,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
if drive.GetName() != "" {
|
||||
spacename := strings.TrimSpace(drive.GetName())
|
||||
if err := validateSpaceName(spacename); err != nil {
|
||||
log.Info().Err(err).Msg("could not update drive: spacename invalid")
|
||||
logger.Info().Err(err).Msg("could not update drive: spacename invalid")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -599,12 +552,12 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
canSetSpaceQuota, err := g.canSetSpaceQuota(r.Context(), user, dt)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not update drive: failed to check if the user can set space quota")
|
||||
logger.Error().Err(err).Msg("could not update drive: failed to check if the user can set space quota")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if !canSetSpaceQuota {
|
||||
log.Debug().
|
||||
logger.Debug().
|
||||
Bool("cansetspacequota", canSetSpaceQuota).
|
||||
Msg("could not update drive: user is not allowed to set the space quota")
|
||||
errorcode.NotAllowed.Render(w, r, http.StatusForbidden, "user is not allowed to set the space quota")
|
||||
@@ -615,10 +568,10 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Interface("payload", updateSpaceRequest).Msg("calling update space on backend")
|
||||
logger.Debug().Interface("payload", updateSpaceRequest).Msg("calling update space on backend")
|
||||
resp, err := gatewayClient.UpdateStorageSpace(r.Context(), updateSpaceRequest)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not update drive: transport error")
|
||||
logger.Error().Err(err).Msg("could not update drive: transport error")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "transport error")
|
||||
return
|
||||
}
|
||||
@@ -626,31 +579,38 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
if resp.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
|
||||
switch resp.Status.GetCode() {
|
||||
case cs3rpc.Code_CODE_NOT_FOUND:
|
||||
log.Debug().Msg("could not update drive: drive not found")
|
||||
logger.Debug().Interface("id", rid).Msg("could not update drive: drive not found")
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found")
|
||||
return
|
||||
case cs3rpc.Code_CODE_PERMISSION_DENIED:
|
||||
log.Debug().Msg("could not update drive, permission denied")
|
||||
logger.Debug().Interface("id", rid).Msg("could not update drive, permission denied")
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found")
|
||||
return
|
||||
case cs3rpc.Code_CODE_INVALID_ARGUMENT:
|
||||
log.Debug().Msg("could not update drive, invalid argument")
|
||||
logger.Debug().Interface("id", rid).Msg("could not update drive, invalid argument")
|
||||
errorcode.NotAllowed.Render(w, r, http.StatusBadRequest, resp.GetStatus().GetMessage())
|
||||
return
|
||||
case cs3rpc.Code_CODE_UNIMPLEMENTED:
|
||||
log.Debug().Msg("could not delete drive: delete not implemented for this type of drive")
|
||||
logger.Debug().Interface("id", rid).Msg("could not delete drive: delete not implemented for this type of drive")
|
||||
errorcode.NotAllowed.Render(w, r, http.StatusMethodNotAllowed, "drive cannot be updated")
|
||||
return
|
||||
default:
|
||||
log.Debug().Str("grpc", resp.GetStatus().GetMessage()).Msg("could not update drive: grpc error")
|
||||
logger.Debug().Interface("id", rid).Str("grpc", resp.GetStatus().GetMessage()).Msg("could not update drive: grpc error")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "grpc error")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(r.Context(), webDavBaseURL, []*storageprovider.StorageSpace{resp.StorageSpace}, APIVersion_1, false)
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not update drive: error parsing grpc response")
|
||||
logger.Error().Err(err).Interface("url", webDavBaseURL.String()).Msg("could not update drive: error parsing url")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(r.Context(), webDavBaseURL, []*storageprovider.StorageSpace{resp.StorageSpace}, APIVersion_1)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("could not update drive: error parsing grpc response")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -659,7 +619,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, spaces[0])
|
||||
}
|
||||
|
||||
func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces []*storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool) ([]*libregraph.Drive, error) {
|
||||
func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces []*storageprovider.StorageSpace, apiVersion APIVersion) ([]*libregraph.Drive, error) {
|
||||
errg, ctx := errgroup.WithContext(ctx)
|
||||
work := make(chan *storageprovider.StorageSpace, len(storageSpaces))
|
||||
results := make(chan *libregraph.Drive, len(storageSpaces))
|
||||
@@ -689,7 +649,7 @@ func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces
|
||||
// skip OCM shares they are no supposed to show up in the drives list
|
||||
continue
|
||||
}
|
||||
res, err := g.cs3StorageSpaceToDrive(ctx, baseURL, storageSpace, apiVersion, expandPermissions)
|
||||
res, err := g.cs3StorageSpaceToDrive(ctx, baseURL, storageSpace, apiVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -773,7 +733,7 @@ func (g Graph) ListStorageSpacesWithFilters(ctx context.Context, filters []*stor
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, space *storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool) (*libregraph.Drive, error) {
|
||||
func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, space *storageprovider.StorageSpace, apiVersion APIVersion) (*libregraph.Drive, error) {
|
||||
logger := g.logger.SubloggerWithRequestID(ctx)
|
||||
if space.Root == nil {
|
||||
logger.Error().Msg("unable to parse space: space has no root")
|
||||
@@ -785,20 +745,18 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa
|
||||
}
|
||||
spaceID := storagespace.FormatResourceID(spaceRid)
|
||||
|
||||
permissions := g.cs3SpacePermissionsToLibreGraph(ctx, space, apiVersion)
|
||||
|
||||
drive := &libregraph.Drive{
|
||||
Id: libregraph.PtrString(spaceID),
|
||||
Name: space.Name,
|
||||
//"createdDateTime": "string (timestamp)", // TODO read from StorageSpace ... needs Opaque for now
|
||||
DriveType: &space.SpaceType,
|
||||
// we currently always expandt the root because it carries the deleted property that indiccates if a space is trashed
|
||||
Root: &libregraph.DriveItem{
|
||||
Id: libregraph.PtrString(storagespace.FormatResourceID(spaceRid)),
|
||||
Id: libregraph.PtrString(storagespace.FormatResourceID(spaceRid)),
|
||||
Permissions: permissions,
|
||||
},
|
||||
}
|
||||
if expandPermissions {
|
||||
drive.Root.Permissions, _ = g.cs3SpacePermissionsToLibreGraph(ctx, space, false, apiVersion)
|
||||
}
|
||||
|
||||
if space.SpaceType == _spaceTypeMountpoint {
|
||||
var remoteItem *libregraph.RemoteItem
|
||||
grantID := storageprovider.ResourceId{
|
||||
|
||||
@@ -79,9 +79,13 @@ var _ = Describe("EducationClass", func() {
|
||||
cfg.Commons = &shared.Commons{}
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
@@ -330,8 +334,12 @@ var _ = Describe("EducationClass", func() {
|
||||
|
||||
cfg.API.GroupMembersPatchLimit = 21
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/mocks"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/config/defaults"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
@@ -79,9 +80,13 @@ var _ = Describe("Schools", func() {
|
||||
cfg.Commons = &shared.Commons{}
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.WithIdentityEducationBackend(identityEducationBackend),
|
||||
)
|
||||
|
||||
@@ -81,9 +81,13 @@ var _ = Describe("EducationUsers", func() {
|
||||
cfg.Commons = &shared.Commons{}
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityEducationBackend(identityEducationBackend),
|
||||
|
||||
@@ -81,9 +81,13 @@ var _ = Describe("Graph", func() {
|
||||
eventsPublisher = mocks.Publisher{}
|
||||
permissionService = mocks.Permissions{}
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.PermissionService(&permissionService),
|
||||
@@ -489,42 +493,8 @@ var _ = Describe("Graph", func() {
|
||||
Expect(libreError.Error.Message).To(Equal("internal quota error"))
|
||||
Expect(libreError.Error.Code).To(Equal(errorcode.GeneralException.String()))
|
||||
})
|
||||
It("omit permissions by default", func() {
|
||||
gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Times(1).Return(&provider.ListStorageSpacesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
StorageSpaces: []*provider.StorageSpace{
|
||||
{
|
||||
Opaque: utils.AppendJSONToOpaque(nil, "grants", map[string]provider.ResourcePermissions{
|
||||
"1": *conversions.NewManagerRole().CS3ResourcePermissions(),
|
||||
}),
|
||||
Root: &provider.ResourceId{},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Return(&gateway.InitiateFileDownloadResponse{
|
||||
Status: status.NewNotFound(ctx, "not found"),
|
||||
}, nil)
|
||||
gatewayClient.On("GetQuota", mock.Anything, mock.Anything).Return(&provider.GetQuotaResponse{
|
||||
Status: status.NewUnimplemented(ctx, fmt.Errorf("not supported"), "not supported"),
|
||||
}, nil)
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&userprovider.GetUserResponse{
|
||||
Status: status.NewUnimplemented(ctx, fmt.Errorf("not supported"), "not supported"),
|
||||
}, nil)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrivesV1(rr, r)
|
||||
|
||||
Expect(rr.Code).To(Equal(http.StatusOK))
|
||||
|
||||
jsonData := gjson.Get(rr.Body.String(), "value")
|
||||
|
||||
Expect(jsonData.Get("#").Num).To(Equal(float64(1)))
|
||||
Expect(jsonData.Get("0.root.permissions").Exists()).To(BeFalse())
|
||||
})
|
||||
})
|
||||
DescribeTable("GetDrivesV1Beta1 and GetAllDrivesV1Beta1 expands root permissions",
|
||||
DescribeTable("GetDrivesV1Beta1 and GetAllDrivesV1Beta1",
|
||||
func(check func(gjson.Result), resourcePermissions provider.ResourcePermissions) {
|
||||
permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settingssvc.GetPermissionByIDResponse{
|
||||
Permission: &v0.Permission{
|
||||
@@ -552,7 +522,7 @@ var _ = Describe("Graph", func() {
|
||||
Status: status.NewUnimplemented(ctx, fmt.Errorf("not supported"), "not supported"),
|
||||
}, nil)
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1beta1.0/me/drives?$expand=root($expand=permissions)", nil)
|
||||
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drives", nil)
|
||||
r = r.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
svc.GetDrivesV1Beta1(rr, r)
|
||||
|
||||
@@ -85,9 +85,13 @@ var _ = Describe("Groups", func() {
|
||||
cfg.Commons = &shared.Commons{}
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
@@ -413,9 +417,13 @@ var _ = Describe("Groups", func() {
|
||||
updatedGroupJson, err := json.Marshal(updatedGroup)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
cfg.API.GroupMembersPatchLimit = 21
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/metadata"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/keycloak"
|
||||
@@ -33,7 +34,6 @@ type Options struct {
|
||||
IdentityBackend identity.Backend
|
||||
IdentityEducationBackend identity.EducationBackend
|
||||
RoleService RoleService
|
||||
UserProfilePhotoService UsersUserProfilePhotoProvider
|
||||
PermissionService Permissions
|
||||
ValueService settingssvc.ValueService
|
||||
RoleManager *roles.Manager
|
||||
@@ -43,6 +43,7 @@ type Options struct {
|
||||
KeycloakClient keycloak.Client
|
||||
EventHistoryClient ehsvc.EventHistoryService
|
||||
TraceProvider trace.TracerProvider
|
||||
Storage metadata.Storage
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -182,9 +183,9 @@ func TraceProvider(val trace.TracerProvider) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// UserProfilePhotoService provides a function to set the UserProfilePhotoService option.
|
||||
func UserProfilePhotoService(p UsersUserProfilePhotoProvider) Option {
|
||||
// MetadataStorage provides a function to set the MetadataStorage option.
|
||||
func MetadataStorage(ms metadata.Storage) Option {
|
||||
return func(o *Options) {
|
||||
o.UserProfilePhotoService = p
|
||||
o.Storage = ms
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,13 @@ var _ = Describe("Users changing their own password", func() {
|
||||
|
||||
eventsPublisher = mocks.Publisher{}
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
|
||||
@@ -17,12 +17,6 @@ import (
|
||||
"github.com/jellydator/ttlcache/v3"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/store"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils/ldap"
|
||||
|
||||
ocldap "github.com/opencloud-eu/opencloud/pkg/ldap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/registry"
|
||||
@@ -32,6 +26,11 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/identity"
|
||||
graphm "github.com/opencloud-eu/opencloud/services/graph/pkg/middleware"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/unifiedrole"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/store"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils/ldap"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -171,7 +170,12 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx
|
||||
return Graph{}, err
|
||||
}
|
||||
|
||||
usersUserProfilePhotoApi, err := NewUsersUserProfilePhotoApi(options.UserProfilePhotoService, options.Logger)
|
||||
usersUserProfilePhotoService, err := NewUsersUserProfilePhotoService(options.Storage)
|
||||
if err != nil {
|
||||
return Graph{}, err
|
||||
}
|
||||
|
||||
usersUserProfilePhotoApi, err := NewUsersUserProfilePhotoApi(usersUserProfilePhotoService, options.Logger)
|
||||
if err != nil {
|
||||
return Graph{}, err
|
||||
}
|
||||
|
||||
@@ -246,8 +246,12 @@ var _ = Describe("sharedbyme", func() {
|
||||
cfg.Commons = &shared.Commons{}
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/mocks"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/config/defaults"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
@@ -70,9 +71,13 @@ var _ = Describe("SharedWithMe", func() {
|
||||
cfg.Commons = &shared.Commons{}
|
||||
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
)
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/identity"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/odata"
|
||||
ocsettingssvc "github.com/opencloud-eu/opencloud/services/settings/pkg/service/v0"
|
||||
"github.com/opencloud-eu/opencloud/services/settings/pkg/store/defaults"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
@@ -54,7 +53,7 @@ func (g Graph) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
exp, err := odata.GetExpandValues(odataReq.Query)
|
||||
exp, err := identity.GetExpandValues(odataReq.Query)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: $expand error")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
@@ -120,85 +119,74 @@ func (g Graph) fetchAppRoleAssignments(ctx context.Context, accountuuid string)
|
||||
|
||||
// GetUserDrive implements the Service interface.
|
||||
func (g Graph) GetUserDrive(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
log := g.logger.SubloggerWithRequestID(ctx).With().Interface("query", r.URL.Query()).Logger()
|
||||
log.Debug().Msg("calling get user drive")
|
||||
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not get personal drive: error parsing webdav base url")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log = log.With().Str("url", webDavBaseURL.String()).Logger()
|
||||
logger := g.logger.SubloggerWithRequestID(r.Context())
|
||||
logger.Debug().Interface("query", r.URL.Query()).Msg("calling get user drive")
|
||||
|
||||
userID, err := url.PathUnescape(chi.URLParam(r, "userID"))
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Str("userID", chi.URLParam(r, "userID")).Msg("could not get drive: unescaping drive id failed")
|
||||
logger.Debug().Err(err).Str("userID", chi.URLParam(r, "userID")).Msg("could not get drive: unescaping drive id failed")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "unescaping user id failed")
|
||||
return
|
||||
}
|
||||
|
||||
log = log.With().Str("userID", userID).Logger()
|
||||
|
||||
_, expandPermissions, err := parseDriveRequest(r)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drives: error parsing odata request")
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if userID == "" {
|
||||
u, ok := revactx.ContextGetUser(ctx)
|
||||
u, ok := revactx.ContextGetUser(r.Context())
|
||||
if !ok {
|
||||
log.Debug().Msg("could not get user: user not in context")
|
||||
logger.Debug().Msg("could not get user: user not in context")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "user not in context")
|
||||
return
|
||||
}
|
||||
userID = u.GetId().GetOpaqueId()
|
||||
}
|
||||
|
||||
log.Debug().Msg("calling list storage spaces with user and personal filter")
|
||||
logger.Debug().Str("userID", userID).Msg("calling list storage spaces with user and personal filter")
|
||||
ctx := r.Context()
|
||||
|
||||
filters := []*storageprovider.ListStorageSpacesRequest_Filter{listStorageSpacesTypeFilter("personal"), listStorageSpacesUserFilter(userID)}
|
||||
res, err := g.ListStorageSpacesWithFilters(ctx, filters, true)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Error().Err(err).Msg("could not get drive: transport error")
|
||||
logger.Error().Err(err).Msg("could not get drive: transport error")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
case res.Status.Code != cs3rpc.Code_CODE_OK:
|
||||
if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
|
||||
// the client is doing a lookup for a specific space, therefore we need to return
|
||||
// not found to the caller
|
||||
log.Debug().Msg("could not get personal drive for user: not found")
|
||||
logger.Debug().Str("userID", userID).Msg("could not get personal drive for user: not found")
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found")
|
||||
return
|
||||
}
|
||||
log.Debug().
|
||||
logger.Debug().
|
||||
Str("userID", userID).
|
||||
Str("grpcmessage", res.GetStatus().GetMessage()).
|
||||
Msg("could not get personal drive for user: grpc error")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
|
||||
return
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions)
|
||||
webDavBaseURL, err := g.getWebDavBaseURL()
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get personal drive: error parsing grpc response")
|
||||
logger.Error().Err(err).Str("url", webDavBaseURL.String()).Msg("could not get personal drive: error parsing webdav base url")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("could not get personal drive: error parsing grpc response")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
switch num := len(spaces); {
|
||||
case num == 0:
|
||||
log.Debug().Msg("could not get personal drive: no drive returned from storage")
|
||||
logger.Debug().Str("userID", userID).Msg("could not get personal drive: no drive returned from storage")
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "no drive returned from storage")
|
||||
return
|
||||
case num == 1:
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, spaces[0])
|
||||
default:
|
||||
log.Debug().Int("number", num).Msg("could not get personal drive: expected to find a single drive but fetched more")
|
||||
logger.Debug().Int("number", num).Msg("could not get personal drive: expected to find a single drive but fetched more")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not get personal drive: expected to find a single drive but fetched more")
|
||||
return
|
||||
}
|
||||
@@ -313,7 +301,7 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
users = finalUsers
|
||||
}
|
||||
|
||||
exp, err := odata.GetExpandValues(odataReq.Query)
|
||||
exp, err := identity.GetExpandValues(odataReq.Query)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: $expand error")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
@@ -461,7 +449,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
exp, err := odata.GetExpandValues(odataReq.Query)
|
||||
exp, err := identity.GetExpandValues(odataReq.Query)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: $expand error")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
|
||||
@@ -478,8 +466,6 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
listDrives := slices.Contains(exp, "drives")
|
||||
listDrive := slices.Contains(exp, "drive")
|
||||
expandDrivePermissions := slices.Contains(exp, "drive.root.permissions")
|
||||
expandDrivesPermissions := slices.Contains(exp, "drives.root.permissions")
|
||||
|
||||
// do we need to list all or only the personal drive
|
||||
filters := []*storageprovider.ListStorageSpacesRequest_Filter{}
|
||||
@@ -540,13 +526,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
user.Drive = &libregraph.Drive{}
|
||||
}
|
||||
for _, sp := range lspr.GetStorageSpaces() {
|
||||
expandPermissions := false
|
||||
if sp.GetSpaceType() == "personal" && sp.GetOwner().GetId().GetOpaqueId() != user.GetId() {
|
||||
expandPermissions = expandDrivePermissions
|
||||
} else {
|
||||
expandPermissions = expandDrivesPermissions
|
||||
}
|
||||
d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp, APIVersion_1, expandPermissions)
|
||||
d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp, APIVersion_1)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Interface("id", sp.Id).Msg("error converting space to drive")
|
||||
continue
|
||||
@@ -1043,7 +1023,7 @@ func (g Graph) searchOCMAcceptedUsers(ctx context.Context, odataReq *godata.GoDa
|
||||
if err != nil {
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
}
|
||||
term, err := odata.GetSearchValues(odataReq.Query)
|
||||
term, err := identity.GetSearchValues(odataReq.Query)
|
||||
if err != nil {
|
||||
return nil, errorcode.New(errorcode.InvalidRequest, err.Error())
|
||||
}
|
||||
|
||||
@@ -95,9 +95,13 @@ var _ = Describe("Users", func() {
|
||||
|
||||
When("OCM is disabled", func() {
|
||||
BeforeEach(func() {
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
@@ -907,8 +911,12 @@ var _ = Describe("Users", func() {
|
||||
|
||||
localCfg.API.UsernameMatch = usernameMatch
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
localSvc, err := service.NewService(
|
||||
service.Config(localCfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
@@ -1129,9 +1137,13 @@ var _ = Describe("Users", func() {
|
||||
BeforeEach(func() {
|
||||
cfg.IncludeOCMSharees = true
|
||||
|
||||
mds := mocks.NewStorage(GinkgoT())
|
||||
mds.EXPECT().Init(mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
var err error
|
||||
svc, err = service.NewService(
|
||||
service.Config(cfg),
|
||||
service.MetadataStorage(mds),
|
||||
service.WithGatewaySelector(gatewaySelector),
|
||||
service.EventsPublisher(&eventsPublisher),
|
||||
service.WithIdentityBackend(identityBackend),
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"build": "node --openssl-legacy-provider scripts/build.js && rm -f build/service-worker.js",
|
||||
"licenses": "NODE_PATH=./node_modules node ../scripts/js-license-ranger.js",
|
||||
"licenses:check": "license-checker-rseidelsohn --summary --relativeLicensePath --onlyAllow 'Python-2.0;Apache*;Apache License, Version 2.0;Apache-2.0;Apache 2.0;Artistic-2.0;BSD;BSD-3-Clause;CC-BY-3.0;CC-BY-4.0;CC0-1.0;ISC;MIT;MPL-2.0;Public Domain;Unicode-TOU;Unlicense;WTFPL;ODC-By-1.0;BlueOak-1.0.0;OFL-1.1' --excludePackages 'identifier;kpop;unicoderegexp' --clarificationsFile license-checker-clarifications.json",
|
||||
"licenses:check": "license-checker-rseidelsohn --summary --relativeLicensePath --onlyAllow 'Python-2.0;Apache*;Apache License, Version 2.0;Apache-2.0;Apache 2.0;Artistic-2.0;BSD;BSD-3-Clause;CC-BY-3.0;CC-BY-4.0;CC0-1.0;ISC;MIT;MPL-2.0;Public Domain;Unicode-TOU;Unlicense;WTFPL;ODC-By-1.0;BlueOak-1.0.0' --excludePackages 'identifier;kpop;unicoderegexp' --clarificationsFile license-checker-clarifications.json",
|
||||
"licenses:csv": "license-checker-rseidelsohn --relativeLicensePath --csv --out ../../third-party-licenses/node/idp/third-party-licenses.csv",
|
||||
"licenses:save": "license-checker-rseidelsohn --relativeLicensePath --out /dev/null --files ../../third-party-licenses/node/idp/third-party-licenses",
|
||||
"lint": "eslint ./**/*.{tsx,ts,jsx,js}",
|
||||
@@ -71,21 +71,21 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.2.5",
|
||||
"@fontsource/roboto": "^5.1.0",
|
||||
"@material-ui/core": "^4.12.4",
|
||||
"@material-ui/icons": "^4.11.3",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.15.30",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.15.19",
|
||||
"@types/react": "^17.0.80",
|
||||
"@types/react-dom": "^17.0.25",
|
||||
"@types/react-redux": "^7.1.33",
|
||||
"@types/redux-logger": "^3.0.13",
|
||||
"axios": "^1.8.2",
|
||||
"classnames": "^2.5.1",
|
||||
"i18next": "^25.2.1",
|
||||
"i18next": "^25.1.2",
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
@@ -102,7 +102,7 @@
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"render-if": "^0.1.1",
|
||||
"web-vitals": "^5.0.2"
|
||||
"web-vitals": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.26.10",
|
||||
@@ -113,7 +113,7 @@
|
||||
"babel-plugin-named-asset-import": "^0.3.8",
|
||||
"babel-preset-react-app": "^10.1.0",
|
||||
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
||||
"cldr": "^7.9.0",
|
||||
"cldr": "^7.5.0",
|
||||
"css-loader": "7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"dotenv": "16.4.7",
|
||||
@@ -126,7 +126,7 @@
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest": "^24.7.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-testing-library": "^3.10.2",
|
||||
"eslint-webpack-plugin": "^3.2.0",
|
||||
@@ -140,13 +140,13 @@
|
||||
"pnp-webpack-plugin": "1.7.0",
|
||||
"postcss-flexbugs-fixes": "5.0.2",
|
||||
"postcss-loader": "4.3.0",
|
||||
"postcss-normalize": "13.0.1",
|
||||
"postcss-normalize": "13.0.0",
|
||||
"postcss-preset-env": "10.1.3",
|
||||
"postcss-safe-parser": "7.0.1",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
"resolve": "1.22.8",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"sass-loader": "^16.0.5",
|
||||
"sass-loader": "^16.0.4",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"typescript": "^5.8.3",
|
||||
"url-loader": "4.1.1",
|
||||
|
||||
1257
services/idp/pnpm-lock.yaml
generated
1257
services/idp/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -105,8 +105,9 @@ When all instances of a federation should trust each other, an `ocmproviders.jso
|
||||
]
|
||||
```
|
||||
|
||||
::: info
|
||||
{{< hint info >}}
|
||||
Note: the `domain` must not contain the protocol as it has to match the [GOCDB site object domain](https://developer.sciencemesh.io/docs/technical-documentation/central-database/#site-object).
|
||||
{{< /hint >}}
|
||||
|
||||
The above federation consists of two instances: `cloud1.opencloud.test` and `cloud2.opencloud.test` that can use the Invitation workflow described below to generate, send and accept invitations.
|
||||
|
||||
@@ -120,8 +121,9 @@ The data backend of the `ocminvitemanager` is configurable. The only supported b
|
||||
|
||||
## Creating Shares
|
||||
|
||||
::: info
|
||||
{{< hint info >}}
|
||||
The below info is outdated as we allow creating federated shares using the graph API. Clients can now discover the available sharing roles and invite federated users using the graph API.
|
||||
{{< /hint >}}
|
||||
|
||||
OCM Shares are currently created using the ocs API, just like regular shares. The difference is the share type, which is 6 (ShareTypeFederatedCloudShare) in this case, and a few additional parameters required for identifying the remote user.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
SHELL := bash
|
||||
NAME := web
|
||||
WEB_ASSETS_VERSION = v3.0.0
|
||||
WEB_ASSETS_VERSION = v2.4.0
|
||||
WEB_ASSETS_BRANCH = main
|
||||
|
||||
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
|
||||
|
||||
@@ -39,7 +39,7 @@ func DefaultConfig() *config.Config {
|
||||
Service: config.Service{
|
||||
Name: "webdav",
|
||||
},
|
||||
OpenCloudPublicURL: "https://localhost:9200",
|
||||
OpenCloudPublicURL: "https://127.0.0.1:9200",
|
||||
WebdavNamespace: "/users/{{.Id.OpaqueId}}",
|
||||
RevaGateway: shared.DefaultRevaConfig().Address,
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -15,17 +14,16 @@ import (
|
||||
merrors "go-micro.dev/v4/errors"
|
||||
"go-micro.dev/v4/metadata"
|
||||
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/tags"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
|
||||
searchmsg "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/search/v0"
|
||||
searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0"
|
||||
"github.com/opencloud-eu/opencloud/services/webdav/pkg/constants"
|
||||
"github.com/opencloud-eu/opencloud/services/webdav/pkg/net"
|
||||
"github.com/opencloud-eu/opencloud/services/webdav/pkg/prop"
|
||||
"github.com/opencloud-eu/opencloud/services/webdav/pkg/propfind"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/tags"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -93,7 +91,7 @@ func (g Webdav) Search(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (g Webdav) sendSearchResponse(rsp *searchsvc.SearchResponse, w http.ResponseWriter, r *http.Request) {
|
||||
logger := g.log.SubloggerWithRequestID(r.Context())
|
||||
responsesXML, err := multistatusResponse(r.Context(), g.config.OpenCloudPublicURL, rsp.Matches)
|
||||
responsesXML, err := multistatusResponse(r.Context(), rsp.Matches)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("error formatting propfind")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -111,10 +109,10 @@ func (g Webdav) sendSearchResponse(rsp *searchsvc.SearchResponse, w http.Respons
|
||||
}
|
||||
|
||||
// multistatusResponse converts a list of matches into a multistatus response string
|
||||
func multistatusResponse(ctx context.Context, publicURL string, matches []*searchmsg.Match) ([]byte, error) {
|
||||
func multistatusResponse(ctx context.Context, matches []*searchmsg.Match) ([]byte, error) {
|
||||
responses := make([]*propfind.ResponseXML, 0, len(matches))
|
||||
for i := range matches {
|
||||
res, err := matchToPropResponse(ctx, publicURL, matches[i])
|
||||
res, err := matchToPropResponse(ctx, matches[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -130,14 +128,14 @@ func multistatusResponse(ctx context.Context, publicURL string, matches []*searc
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func matchToPropResponse(ctx context.Context, publicURL string, match *searchmsg.Match) (*propfind.ResponseXML, error) {
|
||||
// unfortunately, search uses own versions of ResourceId and Ref. So we need to assert them here
|
||||
func matchToPropResponse(ctx context.Context, match *searchmsg.Match) (*propfind.ResponseXML, error) {
|
||||
// unfortunately search uses own versions of ResourceId and Ref. So we need to assert them here
|
||||
var (
|
||||
ref string
|
||||
err error
|
||||
)
|
||||
|
||||
// to copy PROPFIND behaviour, we need to deliver different ids
|
||||
// to copy PROPFIND behaviour we need to deliver different ids
|
||||
// for shares it needs to be sharestorageproviderid!shareid
|
||||
// for other spaces it needs to be storageproviderid$spaceid
|
||||
switch match.Entity.Ref.ResourceId.StorageId {
|
||||
@@ -218,15 +216,6 @@ func matchToPropResponse(ctx context.Context, publicURL string, match *searchmsg
|
||||
score := strconv.FormatFloat(float64(match.Score), 'f', -1, 64)
|
||||
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:score", score))
|
||||
|
||||
if privateURL, err := url.Parse(publicURL); err == nil && match.Entity.Id != nil {
|
||||
privateURL.Path = path.Join(privateURL.Path, "f", storagespace.FormatResourceID(&provider.ResourceId{
|
||||
StorageId: match.Entity.Id.StorageId,
|
||||
SpaceId: match.Entity.Id.SpaceId,
|
||||
OpaqueId: match.Entity.Id.OpaqueId,
|
||||
}))
|
||||
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:privatelink", privateURL.String()))
|
||||
}
|
||||
|
||||
if len(propstatOK.Prop) > 0 {
|
||||
response.Propstat = append(response.Propstat, propstatOK)
|
||||
}
|
||||
|
||||
@@ -2168,7 +2168,6 @@ class GraphHelper {
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $spaceId
|
||||
* @param string|null $query
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
@@ -2178,14 +2177,9 @@ class GraphHelper {
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
string $spaceId,
|
||||
?string $query = null
|
||||
string $spaceId
|
||||
): ResponseInterface {
|
||||
$url = self::getBetaFullUrl($baseUrl, "drives/$spaceId/root/permissions");
|
||||
if ($query !== null) {
|
||||
$url .= "?$query";
|
||||
}
|
||||
|
||||
return HttpRequestHelper::get(
|
||||
$url,
|
||||
$xRequestId,
|
||||
@@ -2474,4 +2468,109 @@ class GraphHelper {
|
||||
self::getRequestHeaders()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $source
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function addUserPhoto(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
string $source
|
||||
): ResponseInterface {
|
||||
$url = self::getFullUrl($baseUrl, "me/photo/\$value");
|
||||
return HttpRequestHelper::put(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
['Content-Type' => 'image/jpeg'],
|
||||
$source
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getUserPhoto(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password
|
||||
): ResponseInterface {
|
||||
$url = self::getFullUrl($baseUrl, "me/photo/\$value");
|
||||
return HttpRequestHelper::get(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $source
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function changeUserPhoto(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
string $source
|
||||
): ResponseInterface {
|
||||
$url = self::getFullUrl($baseUrl, "me/photo/\$value");
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
"PATCH",
|
||||
$user,
|
||||
$password,
|
||||
['Content-Type' => 'image/jpeg'],
|
||||
$source
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function deleteUserPhoto(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password
|
||||
): ResponseInterface {
|
||||
$url = self::getFullUrl($baseUrl, "me/photo/\$value");
|
||||
return HttpRequestHelper::delete(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3178,4 +3178,145 @@ class GraphContext implements Context {
|
||||
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" sets profile photo to "([^"]*)" using the Graph API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $photo
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userSetsUserProfilePhotoUsingTheGraphApi(
|
||||
string $user,
|
||||
string $photo
|
||||
): void {
|
||||
$source = \file_get_contents(
|
||||
$this->featureContext->acceptanceTestsDirLocation() . $photo
|
||||
);
|
||||
$response = GraphHelper::addUserPhoto(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$source
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^user "([^"]*)" has set the profile photo to "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $photo
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theUserHasSetPhoto(string $user, string $photo): void {
|
||||
$response = $this->userSetsUserProfilePhotoUsingTheGraphApi($user, $photo);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" (gets|tries to get) a profile photo using the Graph API$/
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userShouldHasAProfilePhotoUsingTheGraphApi(string $user): void {
|
||||
$response = GraphHelper::getUserPhoto(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user)
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the profile photo should contain file "([^"]*)"$/
|
||||
*
|
||||
* @param string $file
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function profilePhotoShouldContainFile(string $file): void {
|
||||
$source = \file_get_contents(
|
||||
$this->featureContext->acceptanceTestsDirLocation() . $file
|
||||
);
|
||||
Assert::assertEquals(
|
||||
$source,
|
||||
$this->featureContext->getResponse()->getBody()->getContents(),
|
||||
"The profile photo binary does not match expected content of $file"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^for user "([^"]*)" the profile photo should contain file "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function profilePhotoForUserShouldContainFile(string $user, string $file): void {
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(
|
||||
200,
|
||||
"Expected response status code should be 200",
|
||||
$this->userShouldHasAProfilePhotoUsingTheGraphApi($user)
|
||||
);
|
||||
$this->profilePhotoShouldContainFile($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" changes the profile photo to "([^"]*)" using the Graph API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $photo
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userChangesUserProfilePhotoUsingTheGraphApi(
|
||||
string $user,
|
||||
string $photo
|
||||
): void {
|
||||
$source = \file_get_contents(
|
||||
$this->featureContext->acceptanceTestsDirLocation() . $photo
|
||||
);
|
||||
$response = GraphHelper::changeUserPhoto(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$source
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" deletes the profile photo using the Graph API$/
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userDeletesAProfilePhotoUsingTheGraphApi(string $user): void {
|
||||
$response = GraphHelper::deleteUserPhoto(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user)
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -181,37 +181,6 @@ class SharingNgContext implements Context {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $space
|
||||
* @param string|null $query
|
||||
* @param string|null $spaceOwner
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
private function getDrivePermissionsList(
|
||||
string $user,
|
||||
string $space,
|
||||
?string $query = null,
|
||||
?string $spaceOwner = null,
|
||||
): ResponseInterface {
|
||||
if ($spaceOwner) {
|
||||
$spaceId = ($this->spacesContext->getSpaceByName($spaceOwner, $space))["id"];
|
||||
} else {
|
||||
$spaceId = ($this->spacesContext->getSpaceByName($user, $space))["id"];
|
||||
}
|
||||
|
||||
return GraphHelper::getDrivePermissionsList(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$spaceId,
|
||||
$query
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" gets permissions list for (folder|file) "([^"]*)" of the space "([^"]*)" using the Graph API$/
|
||||
*
|
||||
@@ -296,7 +265,7 @@ class SharingNgContext implements Context {
|
||||
public function sendShareInvitation(
|
||||
string $user,
|
||||
array $shareInfo,
|
||||
?string $fileId = null,
|
||||
string $fileId = null,
|
||||
bool $federatedShare = false
|
||||
): ResponseInterface {
|
||||
if ($shareInfo['space'] === 'Personal' || $shareInfo['space'] === 'Shares') {
|
||||
@@ -1607,7 +1576,16 @@ class SharingNgContext implements Context {
|
||||
*
|
||||
*/
|
||||
public function userListsThePermissionsOfDriveUsingRootEndPointOFTheGraphApi(string $user, string $space): void {
|
||||
$this->featureContext->setResponse($this->getDrivePermissionsList($user, $space));
|
||||
$spaceId = ($this->spacesContext->getSpaceByName($user, $space))["id"];
|
||||
|
||||
$response = GraphHelper::getDrivePermissionsList(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$spaceId
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1793,7 +1771,15 @@ class SharingNgContext implements Context {
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userShouldNotHaveAnyPermissionsOnSpace(string $user, string $shareType, string $space): void {
|
||||
$response = $this->getDrivePermissionsList($user, $space);
|
||||
$spaceId = ($this->spacesContext->getSpaceByName($user, $space))["id"];
|
||||
|
||||
$response = GraphHelper::getDrivePermissionsList(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$spaceId
|
||||
);
|
||||
$responseBody = $this->featureContext->getJsonDecodedResponse($response);
|
||||
foreach ($responseBody['value'] as $value) {
|
||||
switch ($shareType) {
|
||||
@@ -1892,7 +1878,16 @@ class SharingNgContext implements Context {
|
||||
string $space,
|
||||
string $spaceOwner
|
||||
): void {
|
||||
$this->featureContext->setResponse($this->getDrivePermissionsList($user, $space, null, $spaceOwner));
|
||||
$spaceId = ($this->spacesContext->getSpaceByName($spaceOwner, $space))["id"];
|
||||
|
||||
$response = GraphHelper::getDrivePermissionsList(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$spaceId
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2123,75 +2118,4 @@ class SharingNgContext implements Context {
|
||||
$this->getPermissionsList($user, $fileOrFolder, $space, $resource, $query)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" gets the permittion list of (folder|file) "([^"]*)" from the space "([^"]*)" using the Graph API with query "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $fileOrFolder (file|folder)
|
||||
* @param string $resource
|
||||
* @param string $space
|
||||
* @param string $query
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userGetsPermissionsListWithQueryForFileOfTheSpaceUsingTheGraphApi(
|
||||
string $user,
|
||||
string $fileOrFolder,
|
||||
string $resource,
|
||||
string $space,
|
||||
string $query
|
||||
): void {
|
||||
$this->featureContext->setResponse(
|
||||
$this->getPermissionsList($user, $fileOrFolder, $space, $resource, $query)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" gets the drive permittion list of the space "([^"]*)" using the Graph API with query "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $space
|
||||
* @param string $query
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userGetsDrivePermissionsListWithQueryUsingTheGraphApi(
|
||||
string $user,
|
||||
string $space,
|
||||
string $query
|
||||
): void {
|
||||
$this->featureContext->setResponse($this->getDrivePermissionsList($user, $space, $query));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the JSON data of the response should (not |)contain the following keys:$/
|
||||
*
|
||||
* @param string|null $shouldOrNot (not| )
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theJsonDataResponseShouldOrNotContainData(string $shouldOrNot, TableNode $table): void {
|
||||
$response = $this->featureContext->getJsonDecodedResponse($this->featureContext->getResponse());
|
||||
|
||||
foreach ($table->getColumn(0) as $key) {
|
||||
$keyExists = \array_key_exists($key, $response);
|
||||
|
||||
if (\trim($shouldOrNot) !== "not") {
|
||||
Assert::assertTrue(
|
||||
$keyExists,
|
||||
"Expected key '$key' to exist in the JSON response, but it doesn't."
|
||||
);
|
||||
} else {
|
||||
Assert::assertFalse(
|
||||
$keyExists,
|
||||
"Key '$key' should not exist in the JSON response, but it does."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1046,7 +1046,7 @@ class SpacesContext implements Context {
|
||||
string $grantedUser,
|
||||
string $role
|
||||
): void {
|
||||
$response = $this->listAllAvailableSpacesOfUser($user, '$expand=root($expand=permissions)');
|
||||
$response = $this->listAllAvailableSpacesOfUser($user);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(
|
||||
200,
|
||||
"Expected response status code should be 200",
|
||||
@@ -4618,7 +4618,7 @@ class SpacesContext implements Context {
|
||||
string $role,
|
||||
?string $expirationDate = null
|
||||
): void {
|
||||
$response = $this->listAllAvailableSpacesOfUser($user, '$expand=root($expand=permissions)');
|
||||
$response = $this->listAllAvailableSpacesOfUser($user);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(
|
||||
200,
|
||||
"Expected response status code should be 200",
|
||||
|
||||
@@ -205,5 +205,27 @@
|
||||
- [apiSearch1/search.feature:466](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L466)
|
||||
- [apiSearch1/search.feature:467](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L467)
|
||||
|
||||
#### [No notification triggered for .zip virus file](https://github.com/opencloud-eu/opencloud/issues/382)
|
||||
- [apiAntivirus/antivirus.feature:41](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L41)
|
||||
- [apiAntivirus/antivirus.feature:43](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L43)
|
||||
- [apiAntivirus/antivirus.feature:45](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L45)
|
||||
- [apiAntivirus/antivirus.feature:69](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L69)
|
||||
- [apiAntivirus/antivirus.feature:71](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L71)
|
||||
- [apiAntivirus/antivirus.feature:73](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L73)
|
||||
- [apiAntivirus/antivirus.feature:115](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L115)
|
||||
- [apiAntivirus/antivirus.feature:117](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L117)
|
||||
- [apiAntivirus/antivirus.feature:119](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L119)
|
||||
- [apiAntivirus/antivirus.feature:141](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L141)
|
||||
- [apiAntivirus/antivirus.feature:143](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L143)
|
||||
- [apiAntivirus/antivirus.feature:145](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L145)
|
||||
- [apiAntivirus/antivirus.feature:169](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L169)
|
||||
- [apiAntivirus/antivirus.feature:171](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L171)
|
||||
- [apiAntivirus/antivirus.feature:173](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L173)
|
||||
- [apiAntivirus/antivirus.feature:199](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L199)
|
||||
- [apiAntivirus/antivirus.feature:201](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L201)
|
||||
- [apiAntivirus/antivirus.feature:203](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L203)
|
||||
- [apiAntivirus/antivirus.feature:228](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L228)
|
||||
- [apiAntivirus/antivirus.feature:253](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L253)
|
||||
|
||||
Note: always have an empty line at the end of this file.
|
||||
The bash script that processes this file requires that the last line has a newline on the end.
|
||||
|
||||
@@ -205,5 +205,27 @@
|
||||
- [apiSearch1/search.feature:466](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L466)
|
||||
- [apiSearch1/search.feature:467](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L467)
|
||||
|
||||
#### [No notification triggered for .zip virus file](https://github.com/opencloud-eu/opencloud/issues/382)
|
||||
- [apiAntivirus/antivirus.feature:41](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L41)
|
||||
- [apiAntivirus/antivirus.feature:43](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L43)
|
||||
- [apiAntivirus/antivirus.feature:45](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L45)
|
||||
- [apiAntivirus/antivirus.feature:69](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L69)
|
||||
- [apiAntivirus/antivirus.feature:71](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L71)
|
||||
- [apiAntivirus/antivirus.feature:73](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L73)
|
||||
- [apiAntivirus/antivirus.feature:115](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L115)
|
||||
- [apiAntivirus/antivirus.feature:117](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L117)
|
||||
- [apiAntivirus/antivirus.feature:119](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L119)
|
||||
- [apiAntivirus/antivirus.feature:141](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L141)
|
||||
- [apiAntivirus/antivirus.feature:143](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L143)
|
||||
- [apiAntivirus/antivirus.feature:145](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L145)
|
||||
- [apiAntivirus/antivirus.feature:169](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L169)
|
||||
- [apiAntivirus/antivirus.feature:171](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L171)
|
||||
- [apiAntivirus/antivirus.feature:173](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L173)
|
||||
- [apiAntivirus/antivirus.feature:199](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L199)
|
||||
- [apiAntivirus/antivirus.feature:201](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L201)
|
||||
- [apiAntivirus/antivirus.feature:203](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L203)
|
||||
- [apiAntivirus/antivirus.feature:228](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L228)
|
||||
- [apiAntivirus/antivirus.feature:253](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L253)
|
||||
|
||||
Note: always have an empty line at the end of this file.
|
||||
The bash script that processes this file requires that the last line has a newline on the end.
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
#### [REPORT request without remote.php returns empty result (only with dav/spaces path)](https://github.com/owncloud/ocis/issues/10329)
|
||||
|
||||
- [apiContract/sharesReport.feature:43](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L43)
|
||||
- [apiContract/sharesReport.feature:68](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L68)
|
||||
- [apiContract/sharesReport.feature:134](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L134)
|
||||
- [apiContract/sharesReport.feature:164](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L164)
|
||||
- [apiContract/sharesReport.feature:42](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L42)
|
||||
- [apiContract/sharesReport.feature:66](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L66)
|
||||
- [apiContract/sharesReport.feature:130](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L130)
|
||||
- [apiContract/sharesReport.feature:159](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L159)
|
||||
- [apiContract/spacesReport.feature:16](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L16)
|
||||
- [apiContract/spacesReport.feature:35](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L35)
|
||||
- [apiContract/spacesReport.feature:55](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L55)
|
||||
- [apiContract/spacesReport.feature:74](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L74)
|
||||
- [apiContract/spacesSharesReport.feature:46](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesSharesReport.feature#L46)
|
||||
- [apiContract/spacesSharesReport.feature:77](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesSharesReport.feature#L77)
|
||||
- [apiContract/spacesReport.feature:34](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L34)
|
||||
- [apiContract/spacesReport.feature:53](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L53)
|
||||
- [apiContract/spacesReport.feature:71](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L71)
|
||||
- [apiContract/spacesSharesReport.feature:45](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesSharesReport.feature#L45)
|
||||
- [apiContract/spacesSharesReport.feature:75](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesSharesReport.feature#L75)
|
||||
- [apiSearch1/dateSearch.feature:19](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L19)
|
||||
- [apiSearch1/dateSearch.feature:39](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L39)
|
||||
- [apiSearch1/dateSearch.feature:40](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L40)
|
||||
@@ -204,17 +204,11 @@
|
||||
- [apiLocks/unlockFiles.feature:322](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L322)
|
||||
- [apiLocks/unlockFiles.feature:323](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L323)
|
||||
- [apiAntivirus/antivirus.feature:114](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L114)
|
||||
- [apiAntivirus/antivirus.feature:115](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L115)
|
||||
- [apiAntivirus/antivirus.feature:116](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L116)
|
||||
- [apiAntivirus/antivirus.feature:117](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L117)
|
||||
- [apiAntivirus/antivirus.feature:118](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L118)
|
||||
- [apiAntivirus/antivirus.feature:119](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L119)
|
||||
- [apiAntivirus/antivirus.feature:140](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L140)
|
||||
- [apiAntivirus/antivirus.feature:141](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L141)
|
||||
- [apiAntivirus/antivirus.feature:142](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L142)
|
||||
- [apiAntivirus/antivirus.feature:143](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L143)
|
||||
- [apiAntivirus/antivirus.feature:144](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L144)
|
||||
- [apiAntivirus/antivirus.feature:145](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L145)
|
||||
- [apiAntivirus/antivirus.feature:356](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L356)
|
||||
- [apiAntivirus/antivirus.feature:357](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L357)
|
||||
- [apiAntivirus/antivirus.feature:358](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L358)
|
||||
|
||||
@@ -32,17 +32,17 @@ Feature: antivirus
|
||||
# antivirus service can scan files during post-processing. on demand scanning is currently not available
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
| message |
|
||||
| Virus found in <new-file-name>. Upload not possible. Virus: Eicar-Signature |
|
||||
And as "Alice" file "<new-file-name>" should not exist
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| dav-path-version | file-name | new-file-name |
|
||||
| old | eicar.com | virusFile1.txt |
|
||||
| old | eicar_com.zip | virusFile2.zip |
|
||||
| new | eicar.com | virusFile1.txt |
|
||||
| new | eicar_com.zip | virusFile2.zip |
|
||||
| spaces | eicar.com | virusFile1.txt |
|
||||
| spaces | eicar_com.zip | virusFile2.zip |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus and a file without virus
|
||||
@@ -53,8 +53,8 @@ Feature: antivirus
|
||||
And user "Alice" uploads file "filesForUpload/textfile.txt" to "/normalfile.txt" using the WebDAV API
|
||||
And the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
| message |
|
||||
| Virus found in <new-file-name>. Upload not possible. Virus: Eicar-Signature |
|
||||
And as "Alice" file "<new-file-name>" should not exist
|
||||
But as "Alice" file "/normalfile.txt" should exist
|
||||
And the content of file "/normalfile.txt" for user "Alice" should be:
|
||||
@@ -64,13 +64,13 @@ Feature: antivirus
|
||||
Cheers.
|
||||
"""
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| dav-path-version | file-name | new-file-name |
|
||||
| old | eicar.com | virusFile1.txt |
|
||||
| old | eicar_com.zip | virusFile2.zip |
|
||||
| new | eicar.com | virusFile1.txt |
|
||||
| new | eicar_com.zip | virusFile2.zip |
|
||||
| spaces | eicar.com | virusFile1.txt |
|
||||
| spaces | eicar_com.zip | virusFile2.zip |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus in chunks
|
||||
@@ -92,7 +92,7 @@ Feature: antivirus
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
@issue-10331 @env-config
|
||||
@issue-10331
|
||||
Scenario Outline: public uploads a file with the virus to a public share
|
||||
Given using <dav-path-version> DAV path
|
||||
And the config "OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD" has been set to "false"
|
||||
@@ -106,17 +106,17 @@ Feature: antivirus
|
||||
When the public uploads file "filesForUpload/filesWithVirus/<file-name>" to "<new-file-name>" inside last link shared folder using the public WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
| message |
|
||||
| Virus found in <new-file-name>. Upload not possible. Virus: Eicar-Signature |
|
||||
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| dav-path-version | file-name | new-file-name |
|
||||
| old | eicar.com | virusFile1.txt |
|
||||
| old | eicar_com.zip | virusFile2.zip |
|
||||
| new | eicar.com | virusFile1.txt |
|
||||
| new | eicar_com.zip | virusFile2.zip |
|
||||
| spaces | eicar.com | virusFile1.txt |
|
||||
| spaces | eicar_com.zip | virusFile2.zip |
|
||||
|
||||
@issue-10331
|
||||
Scenario Outline: public uploads a file with the virus to a password-protected public share
|
||||
@@ -132,17 +132,17 @@ Feature: antivirus
|
||||
When the public uploads file "filesForUpload/filesWithVirus/<file-name>" to "<new-file-name>" inside last link shared folder with password "%public%" using the public WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
| message |
|
||||
| Virus found in <new-file-name>. Upload not possible. Virus: Eicar-Signature |
|
||||
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| dav-path-version | file-name | new-file-name |
|
||||
| old | eicar.com | virusFile1.txt |
|
||||
| old | eicar_com.zip | virusFile2.zip |
|
||||
| new | eicar.com | virusFile1.txt |
|
||||
| new | eicar_com.zip | virusFile2.zip |
|
||||
| spaces | eicar.com | virusFile1.txt |
|
||||
| spaces | eicar_com.zip | virusFile2.zip |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus to a user share
|
||||
@@ -159,18 +159,18 @@ Feature: antivirus
|
||||
When user "Brian" uploads file "filesForUpload/filesWithVirus/<file-name>" to "/Shares/uploadFolder/<new-file-name>" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Brian" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
| message |
|
||||
| Virus found in <new-file-name>. Upload not possible. Virus: Eicar-Signature |
|
||||
And as "Brian" file "/Shares/uploadFolder/<new-file-name>" should not exist
|
||||
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| dav-path-version | file-name | new-file-name |
|
||||
| old | eicar.com | virusFile1.txt |
|
||||
| old | eicar_com.zip | virusFile2.zip |
|
||||
| new | eicar.com | virusFile1.txt |
|
||||
| new | eicar_com.zip | virusFile2.zip |
|
||||
| spaces | eicar.com | virusFile1.txt |
|
||||
| spaces | eicar_com.zip | virusFile2.zip |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus to a group share
|
||||
@@ -189,18 +189,18 @@ Feature: antivirus
|
||||
When user "Brian" uploads file "filesForUpload/filesWithVirus/<file-name>" to "/Shares/uploadFolder/<new-file-name>" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Brian" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
| message |
|
||||
| Virus found in <new-file-name>. Upload not possible. Virus: Eicar-Signature |
|
||||
And as "Brian" file "/Shares/uploadFolder/<new-file-name>" should not exist
|
||||
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| dav-path-version | file-name | new-file-name |
|
||||
| old | eicar.com | virusFile1.txt |
|
||||
| old | eicar_com.zip | virusFile2.zip |
|
||||
| new | eicar.com | virusFile1.txt |
|
||||
| new | eicar_com.zip | virusFile2.zip |
|
||||
| spaces | eicar.com | virusFile1.txt |
|
||||
| spaces | eicar_com.zip | virusFile2.txt |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus to a project space
|
||||
@@ -211,21 +211,21 @@ Feature: antivirus
|
||||
When user "Alice" uploads a file "filesForUpload/filesWithVirus/<file-name>" to "/uploadFolder/<new-file-name>" in space "new-space" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification for resource "<new-file-name>" with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
| message |
|
||||
| Virus found in <new-file-name>. Upload not possible. Virus: Eicar-Signature |
|
||||
And for user "Alice" folder "uploadFolder" of the space "new-space" should not contain these entries:
|
||||
| <new-file-name> |
|
||||
When user "Alice" uploads a file "filesForUpload/filesWithVirus/<file-name>" to "/<new-file-name>" in space "new-space" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification for resource "<new-file-name>" with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
| message |
|
||||
| Virus found in <new-file-name>. Upload not possible. Virus: Eicar-Signature |
|
||||
And for user "Alice" the space "new-space" should not contain these entries:
|
||||
| /<new-file-name> |
|
||||
Examples:
|
||||
| file-name | new-file-name | message |
|
||||
| eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| file-name | new-file-name |
|
||||
| eicar.com | virusFile1.txt |
|
||||
| eicar_com.zip | virusFile2.zip |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus to a shared project space
|
||||
@@ -241,16 +241,16 @@ Feature: antivirus
|
||||
When user "Brian" uploads a file "/filesForUpload/filesWithVirus/<file-name>" to "/<new-file-name>" in space "new-space" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Brian" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
| message |
|
||||
| Virus found in <new-file-name>. Upload not possible. Virus: Eicar-Signature |
|
||||
And for user "Brian" the space "new-space" should not contain these entries:
|
||||
| /<new-file-name> |
|
||||
And for user "Alice" the space "new-space" should not contain these entries:
|
||||
| /<new-file-name> |
|
||||
Examples:
|
||||
| file-name | new-file-name | message |
|
||||
| eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
| eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: Win.Test.EICAR_HDB-1 |
|
||||
| file-name | new-file-name |
|
||||
| eicar.com | virusFile1.txt |
|
||||
| eicar_com.zip | virusFile2.zip |
|
||||
|
||||
@env-config @issue-6494
|
||||
Scenario Outline: upload a file with virus by setting antivirus infected file handling config to continue
|
||||
@@ -481,3 +481,13 @@ Feature: antivirus
|
||||
| Virus found in text.txt. Upload not possible. Virus: Eicar-Signature |
|
||||
And for user "Brian" the content of the file "/text.txt" of the space "new-space" should be "hello world"
|
||||
And for user "Alice" the content of the file "/text.txt" of the space "new-space" should be "hello world"
|
||||
|
||||
|
||||
Scenario Outline: try adding a photo of the user containing the virus
|
||||
When user "Alice" sets profile photo to "filesForUpload/filesWithVirus/eicar-image.jpeg" using the Graph API
|
||||
Then the HTTP status code should be "200"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in eicar-image.jpeg. Upload not possible. Virus: Eicar-Signature |
|
||||
When user "Alice" tries to get a profile photo using the Graph API
|
||||
Then the HTTP status code should be "404"
|
||||
|
||||
@@ -27,15 +27,14 @@ Feature: REPORT request to Shares space
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "SubFolder1" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | SubFolder1 |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | S |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | SubFolder1 |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | S |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
@@ -51,16 +50,15 @@ Feature: REPORT request to Shares space
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "frodo.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | frodo.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | S |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 34 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | frodo.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | S |
|
||||
| d:getcontentlength | 34 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
@@ -107,26 +105,24 @@ Feature: REPORT request to Shares space
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "secureFolder" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:shareroot | /secureFolder |
|
||||
| oc:name | secureFolder |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | SMX |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:size | 14 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
| key | value |
|
||||
| oc:shareroot | /secureFolder |
|
||||
| oc:name | secureFolder |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | SMX |
|
||||
| oc:size | 14 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
When user "Brian" searches for "secure.txt" using the WebDAV API
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
Then the HTTP status code should be "207"
|
||||
And as user "Brian" the REPORT response should contain a resource "secure.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:shareroot | /secureFolder |
|
||||
| oc:name | secure.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | SX |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 14 |
|
||||
| key | value |
|
||||
| oc:shareroot | /secureFolder |
|
||||
| oc:name | secure.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | SX |
|
||||
| d:getcontentlength | 14 |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
@@ -150,13 +146,12 @@ Feature: REPORT request to Shares space
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "secure.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:shareroot | /secure.txt |
|
||||
| oc:name | secure.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | SMX |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 14 |
|
||||
| key | value |
|
||||
| oc:shareroot | /secure.txt |
|
||||
| oc:name | secure.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | SMX |
|
||||
| d:getcontentlength | 14 |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
|
||||
@@ -22,14 +22,13 @@ Feature: REPORT request to project space
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Alice" the REPORT response should contain a resource "testFile.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | testFile.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | RDNVW |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 12 |
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | testFile.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | RDNVW |
|
||||
| d:getcontentlength | 12 |
|
||||
|
||||
@issue-10329
|
||||
Scenario: check the response of the searched sub-file
|
||||
@@ -42,14 +41,13 @@ Feature: REPORT request to project space
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Alice" the REPORT response should contain a resource "insideTheFolder.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | insideTheFolder.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | RDNVW |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 12 |
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | insideTheFolder.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | RDNVW |
|
||||
| d:getcontentlength | 12 |
|
||||
|
||||
@issue-10329
|
||||
Scenario: check the response of the searched folder
|
||||
@@ -61,14 +59,13 @@ Feature: REPORT request to project space
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Alice" the REPORT response should contain a resource "folderMain" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | folderMain |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | RDNVCK |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:size | 0 |
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | folderMain |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | RDNVCK |
|
||||
| oc:size | 0 |
|
||||
|
||||
@issue-10329
|
||||
Scenario: check the response of the searched sub-folder
|
||||
@@ -81,11 +78,10 @@ Feature: REPORT request to project space
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And the HTTP status code should be "207"
|
||||
And as user "Alice" the REPORT response should contain a resource "sub-folder" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | sub-folder |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | RDNVCK |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:size | 0 |
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | sub-folder |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | RDNVCK |
|
||||
| oc:size | 0 |
|
||||
|
||||
@@ -29,16 +29,15 @@ Feature: Report test
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "SubFolder1" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | SubFolder1 |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | S |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:size | 12 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | SubFolder1 |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | S |
|
||||
| oc:size | 12 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
@@ -60,16 +59,15 @@ Feature: Report test
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "insideTheFolder.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | insideTheFolder.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | SD |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 12 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | insideTheFolder.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | SD |
|
||||
| d:getcontentlength | 12 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
Feature: user profile photo
|
||||
As a user, I want to provide my avatar to make my actions more visible
|
||||
|
||||
Background:
|
||||
Given user "Alice" has been created with default attributes
|
||||
|
||||
|
||||
Scenario Outline: add profile photo
|
||||
When user "Alice" sets profile photo to "<file>" using the Graph API
|
||||
Then the HTTP status code should be "<http-status-code>"
|
||||
And for user "Alice" the profile photo should contain file "<file>"
|
||||
Examples:
|
||||
| file | http-status-code |
|
||||
| filesForUpload/testavatar.jpg | 200 |
|
||||
| filesForUpload/testavatar.png | 200 |
|
||||
| filesForUpload/example.gif | 200 |
|
||||
| filesForUpload/lorem.txt | 400 |
|
||||
| filesForUpload/simple.pdf | 400 |
|
||||
| filesForUpload/broken-image-file.png | 400 |
|
||||
|
||||
|
||||
Scenario: user tries to get profile photo when none is set
|
||||
When user "Alice" tries to get a profile photo using the Graph API
|
||||
Then the HTTP status code should be "404"
|
||||
|
||||
|
||||
Scenario Outline: get profile photo
|
||||
Given user "Alice" has set the profile photo to "<file>"
|
||||
When user "Alice" gets a profile photo using the Graph API
|
||||
Then the HTTP status code should be "200"
|
||||
And the profile photo should contain file "<file>"
|
||||
Examples:
|
||||
| file |
|
||||
| filesForUpload/testavatar.jpg |
|
||||
| filesForUpload/testavatar.png |
|
||||
| filesForUpload/example.gif |
|
||||
|
||||
|
||||
Scenario Outline: change profile photo
|
||||
Given user "Alice" has set the profile photo to "filesForUpload/testavatar.jpg"
|
||||
When user "Alice" changes the profile photo to "<file>" using the Graph API
|
||||
Then the HTTP status code should be "<http-status-code>"
|
||||
And for user "Alice" the profile photo should contain file "<file>"
|
||||
Examples:
|
||||
| file | http-status-code |
|
||||
| filesForUpload/testavatar.jpg | 200 |
|
||||
| filesForUpload/testavatar.png | 200 |
|
||||
| filesForUpload/example.gif | 200 |
|
||||
| filesForUpload/lorem.txt | 400 |
|
||||
| filesForUpload/simple.pdf | 400 |
|
||||
| filesForUpload/broken-image-file.png | 400 |
|
||||
|
||||
|
||||
Scenario Outline: delete profile photo
|
||||
Given user "Alice" has set the profile photo to "<file>"
|
||||
When user "Alice" deletes the profile photo using the Graph API
|
||||
Then the HTTP status code should be "200"
|
||||
When user "Alice" tries to get a profile photo using the Graph API
|
||||
Then the HTTP status code should be "404"
|
||||
Examples:
|
||||
| file |
|
||||
| filesForUpload/testavatar.jpg |
|
||||
| filesForUpload/testavatar.png |
|
||||
| filesForUpload/example.gif |
|
||||
@@ -462,8 +462,80 @@ Feature: enable disable permissions role
|
||||
"required": [
|
||||
"eTag",
|
||||
"id",
|
||||
"permissions",
|
||||
"webDavUrl"
|
||||
]
|
||||
],
|
||||
"properties": {
|
||||
"permissions": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["grantedToV2", "roles"],
|
||||
"properties": {
|
||||
"grantedToV2": {
|
||||
"type": "object",
|
||||
"required": ["user"],
|
||||
"properties": {
|
||||
"user" : {
|
||||
"type": "object",
|
||||
"required": ["@libre.graph.userType", "displayName", "id"],
|
||||
"properties": {
|
||||
"@libre.graph.userType": { "const": "Member" },
|
||||
"displayName": { "const": "Alice Hansen" },
|
||||
"id": { "pattern": "^%user_id_pattern%$" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": { "pattern": "^%role_id_pattern%$" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["@libre.graph.permissions.actions", "grantedToV2"],
|
||||
"properties": {
|
||||
"@libre.graph.permissions.actions": {
|
||||
"const": [
|
||||
"libre.graph/driveItem/children/create",
|
||||
"libre.graph/driveItem/standard/delete",
|
||||
"libre.graph/driveItem/path/read",
|
||||
"libre.graph/driveItem/quota/read",
|
||||
"libre.graph/driveItem/content/read",
|
||||
"libre.graph/driveItem/upload/create",
|
||||
"libre.graph/driveItem/permissions/read",
|
||||
"libre.graph/driveItem/children/read",
|
||||
"libre.graph/driveItem/deleted/read",
|
||||
"libre.graph/driveItem/path/update",
|
||||
"libre.graph/driveItem/deleted/update",
|
||||
"libre.graph/driveItem/basic/read"
|
||||
]
|
||||
},
|
||||
"grantedToV2": {
|
||||
"type": "object",
|
||||
"required": ["user"],
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"required": ["@libre.graph.userType", "displayName", "id"],
|
||||
"properties": {
|
||||
"@libre.graph.userType": { "const": "Member" },
|
||||
"displayName": { "const": "Brian Murphy" },
|
||||
"id": { "pattern": "^%user_id_pattern%$" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2250,7 +2250,7 @@ Feature: List a sharing permissions
|
||||
}
|
||||
"""
|
||||
|
||||
@env-config
|
||||
|
||||
Scenario: user lists permissions of a space after enabling 'Space Editor Without Versions' role
|
||||
Given the administrator has enabled the permissions role "Space Editor Without Versions"
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
@@ -2567,206 +2567,3 @@ Feature: List a sharing permissions
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
Scenario: user lists permissions of a folder in personal space with select filter
|
||||
Given user "Brian" has been created with default attributes
|
||||
And user "Alice" has created folder "folder"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | folder |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
When user "Alice" gets the permittion list of folder "folder" from the space "Personal" using the Graph API with query "$select=@libre.graph.permissions.roles.allowedValues"
|
||||
Then the HTTP status code should be "200"
|
||||
And the JSON data of the response should contain the following keys:
|
||||
| @libre.graph.permissions.roles.allowedValues |
|
||||
And the JSON data of the response should not contain the following keys:
|
||||
| @libre.graph.permissions.actions.allowedValues |
|
||||
| value |
|
||||
When user "Alice" gets the permittion list of folder "folder" from the space "Personal" using the Graph API with query "$select=@libre.graph.permissions.actions.allowedValues"
|
||||
Then the HTTP status code should be "200"
|
||||
And the JSON data of the response should contain the following keys:
|
||||
| @libre.graph.permissions.actions.allowedValues |
|
||||
And the JSON data of the response should not contain the following keys:
|
||||
| @libre.graph.permissions.roles.allowedValues |
|
||||
| value |
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"@libre.graph.permissions.actions.allowedValues"
|
||||
],
|
||||
"properties": {
|
||||
"@libre.graph.permissions.actions.allowedValues": {
|
||||
"const": [
|
||||
"libre.graph/driveItem/permissions/create",
|
||||
"libre.graph/driveItem/children/create",
|
||||
"libre.graph/driveItem/standard/delete",
|
||||
"libre.graph/driveItem/path/read",
|
||||
"libre.graph/driveItem/quota/read",
|
||||
"libre.graph/driveItem/content/read",
|
||||
"libre.graph/driveItem/upload/create",
|
||||
"libre.graph/driveItem/permissions/read",
|
||||
"libre.graph/driveItem/children/read",
|
||||
"libre.graph/driveItem/versions/read",
|
||||
"libre.graph/driveItem/deleted/read",
|
||||
"libre.graph/driveItem/path/update",
|
||||
"libre.graph/driveItem/permissions/delete",
|
||||
"libre.graph/driveItem/deleted/delete",
|
||||
"libre.graph/driveItem/versions/update",
|
||||
"libre.graph/driveItem/deleted/update",
|
||||
"libre.graph/driveItem/basic/read",
|
||||
"libre.graph/driveItem/permissions/update",
|
||||
"libre.graph/driveItem/permissions/deny"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
Scenario: user lists permissions of a project space with select filter
|
||||
Given using spaces DAV path
|
||||
And user "Brian" has been created with default attributes
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Viewer |
|
||||
When user "Alice" gets the drive permittion list of the space "new-space" using the Graph API with query "$select=@libre.graph.permissions.actions.allowedValues"
|
||||
Then the HTTP status code should be "200"
|
||||
And the JSON data of the response should contain the following keys:
|
||||
| @libre.graph.permissions.actions.allowedValues |
|
||||
And the JSON data of the response should not contain the following keys:
|
||||
| @libre.graph.permissions.roles.allowedValues |
|
||||
| value |
|
||||
When user "Alice" gets the drive permittion list of the space "new-space" using the Graph API with query "$select=@libre.graph.permissions.roles.allowedValues"
|
||||
Then the HTTP status code should be "200"
|
||||
And the JSON data of the response should contain the following keys:
|
||||
| @libre.graph.permissions.roles.allowedValues |
|
||||
And the JSON data of the response should not contain the following keys:
|
||||
| @libre.graph.permissions.actions.allowedValues |
|
||||
| value |
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"@libre.graph.permissions.roles.allowedValues"
|
||||
],
|
||||
"properties": {
|
||||
"@libre.graph.permissions.roles.allowedValues": {
|
||||
"type": "array",
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"@libre.graph.weight",
|
||||
"description",
|
||||
"displayName",
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"@libre.graph.weight": {
|
||||
"const": 40
|
||||
},
|
||||
"description": {
|
||||
"const": "View and download."
|
||||
},
|
||||
"displayName": {
|
||||
"const": "Can view"
|
||||
},
|
||||
"id": {
|
||||
"const": "a8d5fe5e-96e3-418d-825b-534dbdf22b99"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"@libre.graph.weight",
|
||||
"description",
|
||||
"displayName",
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"@libre.graph.weight": {
|
||||
"const": 90
|
||||
},
|
||||
"description": {
|
||||
"const": "View, download, upload, edit, add, delete including the history."
|
||||
},
|
||||
"displayName": {
|
||||
"const": "Can edit"
|
||||
},
|
||||
"id": {
|
||||
"const": "58c63c02-1d89-4572-916a-870abc5a1b7d"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"@libre.graph.weight",
|
||||
"description",
|
||||
"displayName",
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"@libre.graph.weight": {
|
||||
"const": 120
|
||||
},
|
||||
"description": {
|
||||
"const": "View, download, upload, edit, add, delete and manage members."
|
||||
},
|
||||
"displayName": {
|
||||
"const": "Can manage"
|
||||
},
|
||||
"id": {
|
||||
"const": "312c0871-5ef7-4b3a-85b6-0e4074c64049"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
Scenario: user lists permissions of a project space with count filter
|
||||
Given using spaces DAV path
|
||||
And user "Brian" has been created with default attributes
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Viewer |
|
||||
When user "Alice" gets the drive permittion list of the space "new-space" using the Graph API with query "$count=true&$top=0"
|
||||
Then the HTTP status code should be "200"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"@odata.count"
|
||||
],
|
||||
"properties": {
|
||||
"@odata.count": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
BIN
tests/acceptance/filesForUpload/broken-image-file.png
Normal file
BIN
tests/acceptance/filesForUpload/broken-image-file.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 500 B |
@@ -0,0 +1 @@
|
||||
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||
Binary file not shown.
4
vendor/github.com/blevesearch/bleve/v2/CONTRIBUTING.md
generated
vendored
4
vendor/github.com/blevesearch/bleve/v2/CONTRIBUTING.md
generated
vendored
@@ -2,11 +2,11 @@
|
||||
|
||||
We look forward to your contributions, but ask that you first review these guidelines.
|
||||
|
||||
## Sign the CLA
|
||||
### Sign the CLA
|
||||
|
||||
As Bleve is a Couchbase project we require contributors accept the [Couchbase Contributor License Agreement](http://review.couchbase.org/static/individual_agreement.html). To sign this agreement log into the Couchbase [code review tool](http://review.couchbase.org/). The Bleve project does not use this code review tool but it is still used to track acceptance of the contributor license agreements.
|
||||
|
||||
## Submitting a Pull Request
|
||||
### Submitting a Pull Request
|
||||
|
||||
All types of contributions are welcome, but please keep the following in mind:
|
||||
|
||||
|
||||
43
vendor/github.com/blevesearch/bleve/v2/README.md
generated
vendored
43
vendor/github.com/blevesearch/bleve/v2/README.md
generated
vendored
@@ -16,41 +16,41 @@ A modern indexing + search library in GO
|
||||
* Index any GO data structure or JSON
|
||||
* Intelligent defaults backed up by powerful configuration ([scorch](https://github.com/blevesearch/bleve/blob/master/index/scorch/README.md))
|
||||
* Supported field types:
|
||||
* `text`, `number`, `datetime`, `boolean`, `geopoint`, `geoshape`, `IP`, `vector`
|
||||
* `text`, `number`, `datetime`, `boolean`, `geopoint`, `geoshape`, `IP`, `vector`
|
||||
* Supported query types:
|
||||
* `term`, `phrase`, `match`, `match_phrase`, `prefix`, `regexp`, `wildcard`, `fuzzy`
|
||||
* term range, numeric range, date range, boolean field
|
||||
* compound queries: `conjuncts`, `disjuncts`, boolean (`must`/`should`/`must_not`)
|
||||
* [query string syntax](http://www.blevesearch.com/docs/Query-String-Query/)
|
||||
* [geo spatial search](https://github.com/blevesearch/bleve/blob/master/geo/README.md)
|
||||
* approximate k-nearest neighbors via [vector search](https://github.com/blevesearch/bleve/blob/master/docs/vectors.md)
|
||||
* [synonym search](https://github.com/blevesearch/bleve/blob/master/docs/synonyms.md)
|
||||
* `term`, `phrase`, `match`, `match_phrase`, `prefix`, `regexp`, `wildcard`, `fuzzy`
|
||||
* term range, numeric range, date range, boolean field
|
||||
* compound queries: `conjuncts`, `disjuncts`, boolean (`must`/`should`/`must_not`)
|
||||
* [query string syntax](http://www.blevesearch.com/docs/Query-String-Query/)
|
||||
* [geo spatial search](https://github.com/blevesearch/bleve/blob/master/geo/README.md)
|
||||
* approximate k-nearest neighbors via [vector search](https://github.com/blevesearch/bleve/blob/master/docs/vectors.md)
|
||||
* [synonym search](https://github.com/blevesearch/bleve/blob/master/docs/synonyms.md)
|
||||
* [tf-idf](https://github.com/blevesearch/bleve/blob/master/docs/scoring.md#tf-idf) / [bm25](https://github.com/blevesearch/bleve/blob/master/docs/scoring.md#bm25) scoring models
|
||||
* Hybrid search: exact + semantic
|
||||
* Query time boosting
|
||||
* Search result match highlighting with document fragments
|
||||
* Aggregations/faceting support:
|
||||
* terms facet
|
||||
* numeric range facet
|
||||
* date range facet
|
||||
* terms facet
|
||||
* numeric range facet
|
||||
* date range facet
|
||||
|
||||
## Indexing
|
||||
|
||||
```go
|
||||
message := struct {
|
||||
Id string
|
||||
From string
|
||||
Body string
|
||||
message := struct{
|
||||
Id string
|
||||
From string
|
||||
Body string
|
||||
}{
|
||||
Id: "example",
|
||||
From: "xyz@couchbase.com",
|
||||
Body: "bleve indexing is easy",
|
||||
Id: "example",
|
||||
From: "xyz@couchbase.com",
|
||||
Body: "bleve indexing is easy",
|
||||
}
|
||||
|
||||
mapping := bleve.NewIndexMapping()
|
||||
index, err := bleve.New("example.bleve", mapping)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic(err)
|
||||
}
|
||||
index.Index(message.Id, message)
|
||||
```
|
||||
@@ -69,10 +69,10 @@ searchResult, _ := index.Search(searchRequest)
|
||||
To install the CLI for the latest release of bleve, run:
|
||||
|
||||
```bash
|
||||
go install github.com/blevesearch/bleve/v2/cmd/bleve@latest
|
||||
$ go install github.com/blevesearch/bleve/v2/cmd/bleve@latest
|
||||
```
|
||||
|
||||
```text
|
||||
```
|
||||
$ bleve --help
|
||||
Bleve is a command-line tool to interact with a bleve index.
|
||||
|
||||
@@ -113,7 +113,6 @@ Arabic (ar), Bulgarian (bg), Catalan (ca), Chinese-Japanese-Korean (cjk), Kurdis
|
||||
## Discussion/Issues
|
||||
|
||||
Discuss usage/development of bleve and/or report issues here:
|
||||
|
||||
* [Github issues](https://github.com/blevesearch/bleve/issues)
|
||||
* [Google group](https://groups.google.com/forum/#!forum/bleve)
|
||||
|
||||
|
||||
5
vendor/github.com/blevesearch/bleve/v2/SECURITY.md
generated
vendored
5
vendor/github.com/blevesearch/bleve/v2/SECURITY.md
generated
vendored
@@ -2,12 +2,11 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We support the latest release (for example, bleve v2.5.x).
|
||||
We support the latest release (for example, bleve v2.3.x).
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
All security issues for this project should be reported via email to [security@couchbase.com](mailto:security@couchbase.com) and [fts-team@couchbase.com](mailto:fts-team@couchbase.com).
|
||||
|
||||
All security issues for this project should be reported by email to security@couchbase.com and fts-team@couchbase.com.
|
||||
This mail will be delivered to the owners of this project.
|
||||
|
||||
- To ensure your report is NOT marked as spam, please include the word "security/vulnerability" along with the project name (blevesearch/bleve) in the subject of the email.
|
||||
|
||||
247
vendor/github.com/blevesearch/bleve/v2/geo/README.md
generated
vendored
247
vendor/github.com/blevesearch/bleve/v2/geo/README.md
generated
vendored
@@ -1,7 +1,8 @@
|
||||
# Geo spatial search support in bleve
|
||||
|
||||
Latest bleve spatial capabilities are powered by spatial hierarchical tokens generated from s2geometry.
|
||||
You can find more details about the [s2geometry basics here](http://s2geometry.io/), and explore the extended functionality of our forked golang port of [s2geometry lib here](https://github.com/blevesearch/geo).
|
||||
You can find more details about the [s2geometry basics here](http://s2geometry.io/), and explore the
|
||||
extended functionality of our forked golang port of [s2geometry lib here](https://github.com/blevesearch/geo).
|
||||
|
||||
Users can continue to index and query `geopoint` field type and the existing queries like,
|
||||
|
||||
@@ -13,7 +14,7 @@ as before.
|
||||
|
||||
## New Spatial Field Type - geoshape
|
||||
|
||||
We have introduced a field type (`geoshape`) for representing the new spatial types.
|
||||
We have introduced a field type (`geoshape`) for representing the new spatial types.
|
||||
|
||||
Using the new `geoshape` field type, users can unblock the spatial capabilities
|
||||
for the [geojson](https://datatracker.ietf.org/doc/html/rfc7946) shapes like,
|
||||
@@ -36,7 +37,7 @@ To specify GeoJSON data, use a nested field with:
|
||||
- a field named type that specifies the GeoJSON object type and the type value will be case-insensitive.
|
||||
- a field named coordinates that specifies the object's coordinates.
|
||||
|
||||
```text
|
||||
```
|
||||
"fieldName": {
|
||||
"type": "GeoJSON Type",
|
||||
"coordinates": <coordinates>
|
||||
@@ -49,67 +50,69 @@ To specify GeoJSON data, use a nested field with:
|
||||
- Shapes would be internally represented as geodesics.
|
||||
- The GeoJSON specification strongly suggests splitting geometries so that neither of their parts crosses the antimeridian.
|
||||
|
||||
|
||||
Examples for the various geojson shapes representations are as below.
|
||||
|
||||
## Point
|
||||
|
||||
The following specifies a [Point](https://tools.ietf.org/html/rfc7946#section-3.1.2) field in a document:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "point",
|
||||
"coordinates": [75.05687713623047, 22.53539059204079]
|
||||
}
|
||||
```
|
||||
{
|
||||
"type": "point",
|
||||
"coordinates": [75.05687713623047,22.53539059204079]
|
||||
}
|
||||
```
|
||||
|
||||
## Linestring
|
||||
|
||||
The following specifies a [Linestring](https://tools.ietf.org/html/rfc7946#section-3.1.4) field in a document:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "linestring",
|
||||
"coordinates": [
|
||||
[77.01416015625, 23.0797317624497],
|
||||
[78.134765625, 20.385825381874263]
|
||||
]
|
||||
|
||||
```
|
||||
{
|
||||
"type": "linestring",
|
||||
"coordinates": [
|
||||
[ 77.01416015625, 23.0797317624497],
|
||||
[ 78.134765625, 20.385825381874263]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Polygon
|
||||
|
||||
The following specifies a [Polygon](https://tools.ietf.org/html/rfc7946#section-3.1.6) field in a document:
|
||||
|
||||
```json
|
||||
```
|
||||
{
|
||||
"type": "polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[85.605, 57.207],
|
||||
[86.396, 55.998],
|
||||
[87.033, 56.716],
|
||||
[85.605, 57.207]
|
||||
]
|
||||
]
|
||||
"type": "polygon",
|
||||
"coordinates": [ [ [ 85.605, 57.207],
|
||||
[ 86.396, 55.998],
|
||||
[ 87.033, 56.716],
|
||||
[ 85.605, 57.207]
|
||||
] ]
|
||||
}
|
||||
```
|
||||
|
||||
The first and last coordinates must match in order to close the polygon.
|
||||
|
||||
The first and last coordinates must match in order to close the polygon.
|
||||
And the exterior coordinates have to be in Counter Clockwise Order in a polygon. (CCW)
|
||||
|
||||
|
||||
## MultiPoint
|
||||
|
||||
The following specifies a [Multipoint](https://tools.ietf.org/html/rfc7946#section-3.1.3) field in a document:
|
||||
|
||||
```json
|
||||
```
|
||||
{
|
||||
"type": "multipoint",
|
||||
"coordinates": [
|
||||
[-115.8343505859375, 38.45789034424927],
|
||||
[-115.81237792968749, 38.19502155795575],
|
||||
[-120.80017089843749, 36.54053616262899],
|
||||
[-120.67932128906249, 36.33725319397006]
|
||||
]
|
||||
"type": "multipoint",
|
||||
"coordinates": [
|
||||
[ -115.8343505859375, 38.45789034424927],
|
||||
[ -115.81237792968749, 38.19502155795575],
|
||||
[ -120.80017089843749, 36.54053616262899],
|
||||
[ -120.67932128906249, 36.33725319397006]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -117,23 +120,14 @@ The following specifies a [Multipoint](https://tools.ietf.org/html/rfc7946#secti
|
||||
|
||||
The following specifies a [MultiLineString](https://tools.ietf.org/html/rfc7946#section-3.1.5) field in a document:
|
||||
|
||||
```json
|
||||
```
|
||||
{
|
||||
"type": "multilinestring",
|
||||
"coordinates": [
|
||||
[
|
||||
[-118.31726074, 35.250105158],
|
||||
[-117.509765624, 35.3756141]
|
||||
],
|
||||
[
|
||||
[-118.696289, 34.624167789],
|
||||
[-118.317260742, 35.03899204]
|
||||
],
|
||||
[
|
||||
[-117.9492187, 35.146862906],
|
||||
[-117.6745605, 34.41144164]
|
||||
]
|
||||
]
|
||||
"type": "multilinestring",
|
||||
"coordinates": [
|
||||
[ [ -118.31726074, 35.250105158],[ -117.509765624, 35.3756141] ],
|
||||
[ [ -118.6962890, 34.624167789],[ -118.317260742, 35.03899204] ],
|
||||
[ [ -117.9492187, 35.146862906], [ -117.6745605, 34.41144164] ]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -141,138 +135,112 @@ The following specifies a [MultiLineString](https://tools.ietf.org/html/rfc7946#
|
||||
|
||||
The following specifies a [MultiPolygon](https://tools.ietf.org/html/rfc7946#section-3.1.7) field in a document:
|
||||
|
||||
```json
|
||||
```
|
||||
{
|
||||
"type": "multipolygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
[-73.958, 40.8003],
|
||||
[-73.9498, 40.7968],
|
||||
[-73.9737, 40.7648],
|
||||
[-73.9814, 40.7681],
|
||||
[-73.958, 40.8003]
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
[-73.958, 40.8003],
|
||||
[-73.9498, 40.7968],
|
||||
[-73.9737, 40.7648],
|
||||
[-73.958, 40.8003]
|
||||
]
|
||||
]
|
||||
]
|
||||
"type": "multipolygon",
|
||||
"coordinates": [
|
||||
[ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ],
|
||||
[ -73.9737, 40.7648 ], [ -73.9814, 40.7681 ],
|
||||
[ -73.958, 40.8003 ] ] ],
|
||||
|
||||
|
||||
[ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ],
|
||||
[ -73.9737, 40.7648 ], [ -73.958, 40.8003 ] ] ]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## GeometryCollection
|
||||
|
||||
The following specifies a [GeometryCollection](https://tools.ietf.org/html/rfc7946#section-3.1.8) field in a document:
|
||||
|
||||
```json
|
||||
```
|
||||
{
|
||||
"type": "geometrycollection",
|
||||
"geometries": [
|
||||
"type": "geometrycollection",
|
||||
"geometries": [
|
||||
{
|
||||
"type": "multipoint",
|
||||
"coordinates": [
|
||||
[-73.958, 40.8003],
|
||||
[-73.9498, 40.7968],
|
||||
[-73.9737, 40.7648],
|
||||
[-73.9814, 40.7681]
|
||||
[ -73.9580, 40.8003 ],
|
||||
[ -73.9498, 40.7968 ],
|
||||
[ -73.9737, 40.7648 ],
|
||||
[ -73.9814, 40.7681 ]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "multilinestring",
|
||||
"coordinates": [
|
||||
[
|
||||
[-73.96943, 40.78519],
|
||||
[-73.96082, 40.78095]
|
||||
],
|
||||
[
|
||||
[-73.96415, 40.79229],
|
||||
[-73.95544, 40.78854]
|
||||
],
|
||||
[
|
||||
[-73.97162, 40.78205],
|
||||
[-73.96374, 40.77715]
|
||||
],
|
||||
[
|
||||
[-73.9788, 40.77247],
|
||||
[-73.97036, 40.76811]
|
||||
]
|
||||
[ [ -73.96943, 40.78519 ], [ -73.96082, 40.78095 ] ],
|
||||
[ [ -73.96415, 40.79229 ], [ -73.95544, 40.78854 ] ],
|
||||
[ [ -73.97162, 40.78205 ], [ -73.96374, 40.77715 ] ],
|
||||
[ [ -73.97880, 40.77247 ], [ -73.97036, 40.76811 ] ]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[0, 0],
|
||||
[3, 6],
|
||||
[6, 1],
|
||||
[0, 0]
|
||||
],
|
||||
[
|
||||
[2, 2],
|
||||
[3, 3],
|
||||
[4, 2],
|
||||
[2, 2]
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
"type" : "polygon",
|
||||
"coordinates" : [
|
||||
[ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ],
|
||||
[ [ 2 , 2 ] , [ 3 , 3 ] , [ 4 , 2 ] , [ 2 , 2 ] ]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Circle
|
||||
|
||||
If the user wishes to cover a circular region over the earth's surface, then they could use this shape.
|
||||
If the user wishes to cover a circular region over the earth’s surface, then they could use this shape.
|
||||
A sample circular shape is as below.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "circle",
|
||||
"coordinates": [75.05687713623047, 22.53539059204079],
|
||||
"radius": "1000m"
|
||||
```
|
||||
{
|
||||
"type": "circle",
|
||||
"coordinates": [75.05687713623047,22.53539059204079],
|
||||
"radius": "1000m"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Circle is specified over the center point coordinates along with the radius.
|
||||
Example formats supported for radius are:
|
||||
"5in" , "5inch" , "7yd" , "7yards", "9ft" , "9feet", "11km", "11kilometers", "3nm", "3nauticalmiles", "13mm" , "13millimeters", "15cm", "15centimeters", "17mi", "17miles", "19m" or "19meters".
|
||||
Example formats supported for radius are:
|
||||
"5in" , "5inch" , "7yd" , "7yards", "9ft" , "9feet", "11km", "11kilometers", "3nm"
|
||||
"3nauticalmiles", "13mm" , "13millimeters", "15cm", "15centimeters", "17mi", "17miles" "19m" or "19meters".
|
||||
|
||||
If the unit cannot be determined, the entire string is parsed and the unit of meters is assumed.
|
||||
|
||||
|
||||
## Envelope
|
||||
|
||||
Envelope type, which consists of coordinates for upper left and lower right points of the shape to represent a bounding rectangle in the format [[minLon, maxLat], [maxLon, minLat]].
|
||||
Envelope type, which consists of coordinates for upper left and lower right points of the shape
|
||||
to represent a bounding rectangle in the format [[minLon, maxLat], [maxLon, minLat]].
|
||||
|
||||
```json
|
||||
```
|
||||
{
|
||||
"type": "envelope",
|
||||
"coordinates": [
|
||||
[72.83, 18.979],
|
||||
[78.508, 17.4555]
|
||||
]
|
||||
"type": "envelope",
|
||||
"coordinates": [
|
||||
[72.83, 18.979],
|
||||
[78.508,17.4555]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## GeoShape Query
|
||||
|
||||
Geoshape query support three types/filters of spatial querying capability across those heterogeneous types of documents indexed.
|
||||
Geoshape query support three types/filters of spatial querying capability across those
|
||||
heterogeneous types of documents indexed.
|
||||
|
||||
### Query Structure
|
||||
### Query Structure:
|
||||
|
||||
```json
|
||||
```
|
||||
{
|
||||
"query": {
|
||||
"geometry": {
|
||||
"shape": {
|
||||
"type": "<shapeType>",
|
||||
"coordinates": [
|
||||
[[]]
|
||||
]
|
||||
"type": "<shapeType>",
|
||||
"coordinates": [[[ ]]]
|
||||
},
|
||||
"relation": "<<filterName>>"
|
||||
}
|
||||
@@ -280,6 +248,7 @@ Geoshape query support three types/filters of spatial querying capability across
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
*shapeType* => can be any of the aforementioned types like Point, LineString, Polygon, MultiPoint,
|
||||
Geometrycollection, MultiLineString, MultiPolygon, Circle and Envelope.
|
||||
|
||||
@@ -287,14 +256,16 @@ Geometrycollection, MultiLineString, MultiPolygon, Circle and Envelope.
|
||||
|
||||
### Relation
|
||||
|
||||
| FilterName | Description |
|
||||
| :-----------:| :-----------------------------------------------------------------: |
|
||||
| `intersects` | Return all documents whose shape field intersects the query geometry. |
|
||||
| `contains` | Return all documents whose shape field contains the query geometry |
|
||||
| `within` | Return all documents whose shape field is within the query geometry. |
|
||||
| FilterName | Description |
|
||||
| :-----------:| :-----------------------------------------------------------------: |
|
||||
| `intersects` | Return all documents whose shape field intersects the query geometry. |
|
||||
| `contains` | Return all documents whose shape field contains the query geometry |
|
||||
| `within` | Return all documents whose shape field is within the query geometry. |
|
||||
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
### Older Implementation
|
||||
|
||||
First, all of this geo code is a Go adaptation of the [Lucene 5.3.2 sandbox geo support](https://lucene.apache.org/core/5_3_2/sandbox/org/apache/lucene/util/package-summary.html).
|
||||
|
||||
66
vendor/github.com/blevesearch/bleve/v2/index/scorch/README.md
generated
vendored
66
vendor/github.com/blevesearch/bleve/v2/index/scorch/README.md
generated
vendored
@@ -3,16 +3,13 @@
|
||||
## Definitions
|
||||
|
||||
Batch
|
||||
|
||||
- A collection of Documents to mutate in the index.
|
||||
|
||||
Document
|
||||
|
||||
- Has a unique identifier (arbitrary bytes).
|
||||
- Is comprised of a list of fields.
|
||||
|
||||
Field
|
||||
|
||||
- Has a name (string).
|
||||
- Has a type (text, number, date, geopoint).
|
||||
- Has a value (depending on type).
|
||||
@@ -44,7 +41,7 @@ NOTE: If a document already contains a field \_id, it will be replaced. If this
|
||||
|
||||
### Proposed Structures
|
||||
|
||||
```go
|
||||
```
|
||||
type Segment interface {
|
||||
|
||||
Dictionary(field string) TermDictionary
|
||||
@@ -95,11 +92,9 @@ type IndexSnapshot struct {
|
||||
segment []SegmentSnapshot
|
||||
}
|
||||
```
|
||||
|
||||
**What about errors?**
|
||||
**What about memory mgmnt or context?**
|
||||
**Postings List separate iterator to separate stateful from stateless**
|
||||
|
||||
### Mutating the Index
|
||||
|
||||
The bleve.index API has methods for directly making individual mutations (Update/Delete/SetInternal/DeleteInternal), however for this first implementation, we assume that all of these calls can simply be turned into a Batch of size 1. This may be highly inefficient, but it will be correct. This decision is made based on the fact that Couchbase FTS always uses Batches.
|
||||
@@ -110,9 +105,9 @@ From this point forward, only Batch mutations will be discussed.
|
||||
|
||||
Sequence of Operations:
|
||||
|
||||
1. For each document in the batch, search through all existing segments. The goal is to build up a per-segment bitset which tells us which documents in that segment are obsoleted by the addition of the new segment we're currently building. NOTE: we're not ready for this change to take effect yet, so rather than this operation mutating anything, they simply return bitsets, which we can apply later. Logically, this is something like:
|
||||
1. For each document in the batch, search through all existing segments. The goal is to build up a per-segment bitset which tells us which documents in that segment are obsoleted by the addition of the new segment we're currently building. NOTE: we're not ready for this change to take effect yet, so rather than this operation mutating anything, they simply return bitsets, which we can apply later. Logically, this is something like:
|
||||
|
||||
```go
|
||||
```
|
||||
foreach segment {
|
||||
dict := segment.Dictionary("\_id")
|
||||
postings := empty postings list
|
||||
@@ -124,21 +119,21 @@ Sequence of Operations:
|
||||
|
||||
NOTE: it is illustrated above as nested for loops, but some or all of these could be concurrently. The end result is that for each segment, we have (possibly empty) bitset.
|
||||
|
||||
2. Also concurrent with 1, the documents in the batch are analyzed. This analysis proceeds using the existing analyzer pool.
|
||||
2. Also concurrent with 1, the documents in the batch are analyzed. This analysis proceeds using the existing analyzer pool.
|
||||
|
||||
3. (after 2 completes) Analyzed documents are fed into a function which builds a new Segment representing this information.
|
||||
|
||||
4. We now have everything we need to update the state of the system to include this new snapshot.
|
||||
- Acquire a lock
|
||||
- Create a new IndexSnapshot
|
||||
- For each SegmentSnapshot in the IndexSnapshot, take the deleted PostingsList and OR it with the new postings list for this Segment. Construct a new SegmentSnapshot for the segment using this new deleted PostingsList. Append this SegmentSnapshot to the IndexSnapshot.
|
||||
- Create a new SegmentSnapshot wrapping our new segment with nil deleted docs.
|
||||
- Append the new SegmentSnapshot to the IndexSnapshot
|
||||
- Release the lock
|
||||
4. We now have everything we need to update the state of the system to include this new snapshot.
|
||||
|
||||
- Acquire a lock
|
||||
- Create a new IndexSnapshot
|
||||
- For each SegmentSnapshot in the IndexSnapshot, take the deleted PostingsList and OR it with the new postings list for this Segment. Construct a new SegmentSnapshot for the segment using this new deleted PostingsList. Append this SegmentSnapshot to the IndexSnapshot.
|
||||
- Create a new SegmentSnapshot wrapping our new segment with nil deleted docs.
|
||||
- Append the new SegmentSnapshot to the IndexSnapshot
|
||||
- Release the lock
|
||||
|
||||
An ASCII art example:
|
||||
|
||||
```text
|
||||
```
|
||||
0 - Empty Index
|
||||
|
||||
No segments
|
||||
@@ -214,7 +209,7 @@ Term search is the only searching primitive exposed in today's bleve.index API.
|
||||
|
||||
A term search for term T in field F will look something like this:
|
||||
|
||||
```go
|
||||
```
|
||||
searchResultPostings = empty
|
||||
foreach segment {
|
||||
dict := segment.Dictionary(F)
|
||||
@@ -227,31 +222,31 @@ The searchResultPostings will be a new implementation of the TermFieldReader int
|
||||
|
||||
As a reminder this interface is:
|
||||
|
||||
```go
|
||||
```
|
||||
// TermFieldReader is the interface exposing the enumeration of documents
|
||||
// containing a given term in a given field. Documents are returned in byte
|
||||
// lexicographic order over their identifiers.
|
||||
type TermFieldReader interface {
|
||||
// Next returns the next document containing the term in this field, or nil
|
||||
// when it reaches the end of the enumeration. The preAlloced TermFieldDoc
|
||||
// is optional, and when non-nil, will be used instead of allocating memory.
|
||||
Next(preAlloced *TermFieldDoc) (*TermFieldDoc, error)
|
||||
// Next returns the next document containing the term in this field, or nil
|
||||
// when it reaches the end of the enumeration. The preAlloced TermFieldDoc
|
||||
// is optional, and when non-nil, will be used instead of allocating memory.
|
||||
Next(preAlloced *TermFieldDoc) (*TermFieldDoc, error)
|
||||
|
||||
// Advance resets the enumeration at specified document or its immediate
|
||||
// follower.
|
||||
Advance(ID IndexInternalID, preAlloced *TermFieldDoc) (*TermFieldDoc, error)
|
||||
// Advance resets the enumeration at specified document or its immediate
|
||||
// follower.
|
||||
Advance(ID IndexInternalID, preAlloced *TermFieldDoc) (*TermFieldDoc, error)
|
||||
|
||||
// Count returns the number of documents contains the term in this field.
|
||||
Count() uint64
|
||||
Close() error
|
||||
// Count returns the number of documents contains the term in this field.
|
||||
Count() uint64
|
||||
Close() error
|
||||
}
|
||||
```
|
||||
|
||||
At first glance this appears problematic, we have no way to return documents in order of their identifiers. But it turns out the wording of this perhaps too strong, or a bit ambiguous. Originally, this referred to the external identifiers, but with the introduction of a distinction between internal/external identifiers, returning them in order of their internal identifiers is also acceptable. **ASIDE**: the reason for this is that most callers just use Next() and literally don't care what the order is, they could be in any order and it would be fine. There is only one search that cares and that is the ConjunctionSearcher, which relies on Next/Advance having very specific semantics. Later in this document we will have a proposal to split into multiple interfaces:
|
||||
|
||||
- The weakest interface, only supports Next() no ordering at all.
|
||||
- Ordered, supporting Advance()
|
||||
- And/Or'able capable of internally efficiently doing these ops with like interfaces (if not capable then can always fall back to external walking)
|
||||
- The weakest interface, only supports Next() no ordering at all.
|
||||
- Ordered, supporting Advance()
|
||||
- And/Or'able capable of internally efficiently doing these ops with like interfaces (if not capable then can always fall back to external walking)
|
||||
|
||||
But, the good news is that we don't even have to do that for our first implementation. As long as the global numbers we use for internal identifiers are consistent within this IndexSnapshot, then Next() will be ordered by ascending document number, and Advance() will still work correctly.
|
||||
|
||||
@@ -259,7 +254,7 @@ NOTE: there is another place where we rely on the ordering of these hits, and th
|
||||
|
||||
An ASCII art example:
|
||||
|
||||
```text
|
||||
```
|
||||
Let's start with the IndexSnapshot we ended with earlier:
|
||||
|
||||
3 - Index Batch [ C' ]
|
||||
@@ -325,6 +320,7 @@ In the future, interfaces to detect these non-serially operating TermFieldReader
|
||||
|
||||
Another related topic is that of peak memory usage. With serially operating TermFieldReaders it was necessary to start them all at the same time and operate in unison. However, with these non-serially operating TermFieldReaders we have the option of doing a few at a time, consolidating them, dispoting the intermediaries, and then doing a few more. For very complex queries with many clauses this could reduce peak memory usage.
|
||||
|
||||
|
||||
### Memory Tracking
|
||||
|
||||
All segments must be able to produce two statistics, an estimate of their explicit memory usage, and their actual size on disk (if any). For in-memory segments, disk usage could be zero, and the memory usage represents the entire information content. For mmap-based disk segments, the memory could be as low as the size of tracking structure itself (say just a few pointers).
|
||||
@@ -339,12 +335,14 @@ At runtime, the state of an index (it's IndexSnapshot) is not only the contents
|
||||
|
||||
This also relates to the topic rollback, addressed next...
|
||||
|
||||
|
||||
### Rollback
|
||||
|
||||
One desirable property in the Couchbase ecosystem is the ability to rollback to some previous (though typically not long ago) state. One idea for keeping this property in this design is to protect some of the most recent segments from merging. Then, if necessary, they could be "undone" to reveal previous states of the system. In these scenarios "undone" has to properly undo the deleted bitmasks on the other segments. Again, the current thinking is that rather than "undo" anything, it could be work that was deferred in the first place, thus making it easier to logically undo.
|
||||
|
||||
Another possibly related approach would be to tie this into our existing snapshot mechanism. Perhaps simulating a slow reader (holding onto index snapshots) for some period of time, can be the mechanism to achieve the desired end goal.
|
||||
|
||||
|
||||
### Internal Storage
|
||||
|
||||
The bleve.index API has support for "internal storage". The ability to store information under a separate name space.
|
||||
|
||||
6
vendor/github.com/blevesearch/bleve/v2/index/scorch/mergeplan/merge_plan.go
generated
vendored
6
vendor/github.com/blevesearch/bleve/v2/index/scorch/mergeplan/merge_plan.go
generated
vendored
@@ -295,10 +295,8 @@ func plan(segmentsIn []Segment, o *MergePlanOptions) (*MergePlan, error) {
|
||||
if len(bestRoster) == 0 {
|
||||
return rv, nil
|
||||
}
|
||||
// create tasks with valid merges - i.e. there should be atleast 2 non-empty segments
|
||||
if len(bestRoster) > 1 {
|
||||
rv.Tasks = append(rv.Tasks, &MergeTask{Segments: bestRoster})
|
||||
}
|
||||
|
||||
rv.Tasks = append(rv.Tasks, &MergeTask{Segments: bestRoster})
|
||||
|
||||
eligibles = removeSegments(eligibles, bestRoster)
|
||||
}
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/index/scorch/optimize.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/index/scorch/optimize.go
generated
vendored
@@ -393,7 +393,5 @@ func (i *IndexSnapshot) unadornedTermFieldReader(
|
||||
includeNorm: false,
|
||||
includeTermVectors: false,
|
||||
recycle: false,
|
||||
// signal downstream that this is a special unadorned termFieldReader
|
||||
unadorned: true,
|
||||
}
|
||||
}
|
||||
|
||||
30
vendor/github.com/blevesearch/bleve/v2/index/scorch/snapshot_index_tfr.go
generated
vendored
30
vendor/github.com/blevesearch/bleve/v2/index/scorch/snapshot_index_tfr.go
generated
vendored
@@ -50,7 +50,6 @@ type IndexSnapshotTermFieldReader struct {
|
||||
recycle bool
|
||||
bytesRead uint64
|
||||
ctx context.Context
|
||||
unadorned bool
|
||||
}
|
||||
|
||||
func (i *IndexSnapshotTermFieldReader) incrementBytesRead(val uint64) {
|
||||
@@ -147,29 +146,14 @@ func (i *IndexSnapshotTermFieldReader) Advance(ID index.IndexInternalID, preAllo
|
||||
// FIXME do something better
|
||||
// for now, if we need to seek backwards, then restart from the beginning
|
||||
if i.currPosting != nil && bytes.Compare(i.currID, ID) >= 0 {
|
||||
// Check if the TFR is a special unadorned composite optimization.
|
||||
// Such a TFR will NOT have a valid `term` or `field` set, making it
|
||||
// impossible for the TFR to replace itself with a new one.
|
||||
if !i.unadorned {
|
||||
i2, err := i.snapshot.TermFieldReader(context.TODO(), i.term, i.field,
|
||||
i.includeFreq, i.includeNorm, i.includeTermVectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// close the current term field reader before replacing it with a new one
|
||||
_ = i.Close()
|
||||
*i = *(i2.(*IndexSnapshotTermFieldReader))
|
||||
} else {
|
||||
// unadorned composite optimization
|
||||
// we need to reset all the iterators
|
||||
// back to the beginning, which effectively
|
||||
// achives the same thing as the above
|
||||
for _, iter := range i.iterators {
|
||||
if optimizedIterator, ok := iter.(ResetablePostingsIterator); ok {
|
||||
optimizedIterator.ResetIterator()
|
||||
}
|
||||
}
|
||||
i2, err := i.snapshot.TermFieldReader(context.TODO(), i.term, i.field,
|
||||
i.includeFreq, i.includeNorm, i.includeTermVectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// close the current term field reader before replacing it with a new one
|
||||
_ = i.Close()
|
||||
*i = *(i2.(*IndexSnapshotTermFieldReader))
|
||||
}
|
||||
num, err := docInternalToNumber(ID)
|
||||
if err != nil {
|
||||
|
||||
21
vendor/github.com/blevesearch/bleve/v2/index/scorch/unadorned.go
generated
vendored
21
vendor/github.com/blevesearch/bleve/v2/index/scorch/unadorned.go
generated
vendored
@@ -96,12 +96,6 @@ func (i *unadornedPostingsIteratorBitmap) ReplaceActual(actual *roaring.Bitmap)
|
||||
i.actual = actual.Iterator()
|
||||
}
|
||||
|
||||
// Resets the iterator to the beginning of the postings list.
|
||||
// by resetting the actual iterator.
|
||||
func (i *unadornedPostingsIteratorBitmap) ResetIterator() {
|
||||
i.actual = i.actualBM.Iterator()
|
||||
}
|
||||
|
||||
func newUnadornedPostingsIteratorFromBitmap(bm *roaring.Bitmap) segment.PostingsIterator {
|
||||
return &unadornedPostingsIteratorBitmap{
|
||||
actualBM: bm,
|
||||
@@ -112,8 +106,7 @@ func newUnadornedPostingsIteratorFromBitmap(bm *roaring.Bitmap) segment.Postings
|
||||
const docNum1HitFinished = math.MaxUint64
|
||||
|
||||
type unadornedPostingsIterator1Hit struct {
|
||||
docNumOrig uint64 // original 1-hit docNum used to create this iterator
|
||||
docNum uint64 // current docNum
|
||||
docNum uint64
|
||||
}
|
||||
|
||||
func (i *unadornedPostingsIterator1Hit) Next() (segment.Posting, error) {
|
||||
@@ -160,22 +153,12 @@ func (i *unadornedPostingsIterator1Hit) BytesWritten() uint64 {
|
||||
|
||||
func (i *unadornedPostingsIterator1Hit) ResetBytesRead(uint64) {}
|
||||
|
||||
// ResetIterator resets the iterator to the original state.
|
||||
func (i *unadornedPostingsIterator1Hit) ResetIterator() {
|
||||
i.docNum = i.docNumOrig
|
||||
}
|
||||
|
||||
func newUnadornedPostingsIteratorFrom1Hit(docNum1Hit uint64) segment.PostingsIterator {
|
||||
return &unadornedPostingsIterator1Hit{
|
||||
docNumOrig: docNum1Hit,
|
||||
docNum: docNum1Hit,
|
||||
docNum1Hit,
|
||||
}
|
||||
}
|
||||
|
||||
type ResetablePostingsIterator interface {
|
||||
ResetIterator()
|
||||
}
|
||||
|
||||
type UnadornedPosting uint64
|
||||
|
||||
func (p UnadornedPosting) Number() uint64 {
|
||||
|
||||
17
vendor/github.com/blevesearch/bleve/v2/search/collector/topn.go
generated
vendored
17
vendor/github.com/blevesearch/bleve/v2/search/collector/topn.go
generated
vendored
@@ -20,6 +20,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/numeric"
|
||||
"github.com/blevesearch/bleve/v2/search"
|
||||
"github.com/blevesearch/bleve/v2/size"
|
||||
index "github.com/blevesearch/bleve_index_api"
|
||||
@@ -500,7 +501,23 @@ func (hc *TopNCollector) finalizeResults(r index.IndexReader) error {
|
||||
doc.Complete(nil)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode geo sort keys back to its distance values
|
||||
for i, so := range hc.sort {
|
||||
if _, ok := so.(*search.SortGeoDistance); ok {
|
||||
for _, dm := range hc.results {
|
||||
// The string is a int64 bit representation of a float64 distance
|
||||
distInt, err := numeric.PrefixCoded(dm.Sort[i]).Int64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dm.Sort[i] = strconv.FormatFloat(numeric.Int64ToFloat64(distInt), 'f', -1, 64)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
6
vendor/github.com/blevesearch/bleve/v2/search/search.go
generated
vendored
6
vendor/github.com/blevesearch/bleve/v2/search/search.go
generated
vendored
@@ -154,7 +154,6 @@ type DocumentMatch struct {
|
||||
Locations FieldTermLocationMap `json:"locations,omitempty"`
|
||||
Fragments FieldFragmentMap `json:"fragments,omitempty"`
|
||||
Sort []string `json:"sort,omitempty"`
|
||||
DecodedSort []string `json:"decoded_sort,omitempty"`
|
||||
|
||||
// Fields contains the values for document fields listed in
|
||||
// SearchRequest.Fields. Text fields are returned as strings, numeric
|
||||
@@ -225,7 +224,6 @@ func (dm *DocumentMatch) Reset() *DocumentMatch {
|
||||
dm.IndexInternalID = indexInternalID[:0]
|
||||
// reuse the []interface{} already allocated (and reset len to 0)
|
||||
dm.Sort = sort[:0]
|
||||
dm.DecodedSort = dm.DecodedSort[:0]
|
||||
// reuse the FieldTermLocations already allocated (and reset len to 0)
|
||||
dm.FieldTermLocations = ftls[:0]
|
||||
return dm
|
||||
@@ -265,10 +263,6 @@ func (dm *DocumentMatch) Size() int {
|
||||
sizeInBytes += size.SizeOfString + len(entry)
|
||||
}
|
||||
|
||||
for _, entry := range dm.DecodedSort {
|
||||
sizeInBytes += size.SizeOfString + len(entry)
|
||||
}
|
||||
|
||||
for k := range dm.Fields {
|
||||
sizeInBytes += size.SizeOfString + len(k) +
|
||||
size.SizeOfPtr
|
||||
|
||||
42
vendor/github.com/blevesearch/bleve/v2/search/sort.go
generated
vendored
42
vendor/github.com/blevesearch/bleve/v2/search/sort.go
generated
vendored
@@ -20,9 +20,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/geo"
|
||||
@@ -38,7 +36,6 @@ var (
|
||||
type SearchSort interface {
|
||||
UpdateVisitor(field string, term []byte)
|
||||
Value(a *DocumentMatch) string
|
||||
DecodeValue(value string) string
|
||||
Descending() bool
|
||||
|
||||
RequiresDocID() bool
|
||||
@@ -215,9 +212,7 @@ type SortOrder []SearchSort
|
||||
|
||||
func (so SortOrder) Value(doc *DocumentMatch) {
|
||||
for _, soi := range so {
|
||||
value := soi.Value(doc)
|
||||
doc.Sort = append(doc.Sort, value)
|
||||
doc.DecodedSort = append(doc.DecodedSort, soi.DecodeValue(value))
|
||||
doc.Sort = append(doc.Sort, soi.Value(doc))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,25 +390,6 @@ func (s *SortField) Value(i *DocumentMatch) string {
|
||||
return iTerm
|
||||
}
|
||||
|
||||
func (s *SortField) DecodeValue(value string) string {
|
||||
switch s.Type {
|
||||
case SortFieldAsNumber:
|
||||
i64, err := numeric.PrefixCoded(value).Int64()
|
||||
if err != nil {
|
||||
return value
|
||||
}
|
||||
return strconv.FormatFloat(numeric.Int64ToFloat64(i64), 'f', -1, 64)
|
||||
case SortFieldAsDate:
|
||||
i64, err := numeric.PrefixCoded(value).Int64()
|
||||
if err != nil {
|
||||
return value
|
||||
}
|
||||
return time.Unix(0, i64).UTC().String()
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
// Descending determines the order of the sort
|
||||
func (s *SortField) Descending() bool {
|
||||
return s.Desc
|
||||
@@ -569,10 +545,6 @@ func (s *SortDocID) Value(i *DocumentMatch) string {
|
||||
return i.ID
|
||||
}
|
||||
|
||||
func (s *SortDocID) DecodeValue(value string) string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Descending determines the order of the sort
|
||||
func (s *SortDocID) Descending() bool {
|
||||
return s.Desc
|
||||
@@ -618,10 +590,6 @@ func (s *SortScore) Value(i *DocumentMatch) string {
|
||||
return "_score"
|
||||
}
|
||||
|
||||
func (s *SortScore) DecodeValue(value string) string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Descending determines the order of the sort
|
||||
func (s *SortScore) Descending() bool {
|
||||
return s.Desc
|
||||
@@ -726,14 +694,6 @@ func (s *SortGeoDistance) Value(i *DocumentMatch) string {
|
||||
return string(numeric.MustNewPrefixCodedInt64(distInt64, 0))
|
||||
}
|
||||
|
||||
func (s *SortGeoDistance) DecodeValue(value string) string {
|
||||
distInt, err := numeric.PrefixCoded(value).Int64()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strconv.FormatFloat(numeric.Int64ToFloat64(distInt), 'f', -1, 64)
|
||||
}
|
||||
|
||||
// Descending determines the order of the sort
|
||||
func (s *SortGeoDistance) Descending() bool {
|
||||
return s.Desc
|
||||
|
||||
30
vendor/github.com/blevesearch/zapx/v16/docvalues.go
generated
vendored
30
vendor/github.com/blevesearch/zapx/v16/docvalues.go
generated
vendored
@@ -106,7 +106,7 @@ func (di *docValueReader) curChunkNumber() uint64 {
|
||||
return di.curChunkNum
|
||||
}
|
||||
|
||||
func (sb *SegmentBase) loadFieldDocValueReader(field string,
|
||||
func (s *SegmentBase) loadFieldDocValueReader(field string,
|
||||
fieldDvLocStart, fieldDvLocEnd uint64) (*docValueReader, error) {
|
||||
// get the docValue offset for the given fields
|
||||
if fieldDvLocStart == fieldNotUninverted {
|
||||
@@ -118,15 +118,15 @@ func (sb *SegmentBase) loadFieldDocValueReader(field string,
|
||||
var numChunks, chunkOffsetsPosition uint64
|
||||
|
||||
if fieldDvLocEnd-fieldDvLocStart > 16 {
|
||||
numChunks = binary.BigEndian.Uint64(sb.mem[fieldDvLocEnd-8 : fieldDvLocEnd])
|
||||
numChunks = binary.BigEndian.Uint64(s.mem[fieldDvLocEnd-8 : fieldDvLocEnd])
|
||||
// read the length of chunk offsets
|
||||
chunkOffsetsLen := binary.BigEndian.Uint64(sb.mem[fieldDvLocEnd-16 : fieldDvLocEnd-8])
|
||||
chunkOffsetsLen := binary.BigEndian.Uint64(s.mem[fieldDvLocEnd-16 : fieldDvLocEnd-8])
|
||||
// acquire position of chunk offsets
|
||||
chunkOffsetsPosition = (fieldDvLocEnd - 16) - chunkOffsetsLen
|
||||
|
||||
// 16 bytes since it corresponds to the length
|
||||
// of chunk offsets and the position of the offsets
|
||||
sb.incrementBytesRead(16)
|
||||
s.incrementBytesRead(16)
|
||||
} else {
|
||||
return nil, fmt.Errorf("loadFieldDocValueReader: fieldDvLoc too small: %d-%d", fieldDvLocEnd, fieldDvLocStart)
|
||||
}
|
||||
@@ -140,14 +140,14 @@ func (sb *SegmentBase) loadFieldDocValueReader(field string,
|
||||
// read the chunk offsets
|
||||
var offset uint64
|
||||
for i := 0; i < int(numChunks); i++ {
|
||||
loc, read := binary.Uvarint(sb.mem[chunkOffsetsPosition+offset : chunkOffsetsPosition+offset+binary.MaxVarintLen64])
|
||||
loc, read := binary.Uvarint(s.mem[chunkOffsetsPosition+offset : chunkOffsetsPosition+offset+binary.MaxVarintLen64])
|
||||
if read <= 0 {
|
||||
return nil, fmt.Errorf("corrupted chunk offset during segment load")
|
||||
}
|
||||
fdvIter.chunkOffsets[i] = loc
|
||||
offset += uint64(read)
|
||||
}
|
||||
sb.incrementBytesRead(offset)
|
||||
s.incrementBytesRead(offset)
|
||||
// set the data offset
|
||||
fdvIter.dvDataLoc = fieldDvLocStart
|
||||
return fdvIter, nil
|
||||
@@ -286,15 +286,15 @@ func (di *docValueReader) getDocValueLocs(docNum uint64) (uint64, uint64) {
|
||||
|
||||
// VisitDocValues is an implementation of the
|
||||
// DocValueVisitable interface
|
||||
func (sb *SegmentBase) VisitDocValues(localDocNum uint64, fields []string,
|
||||
func (s *SegmentBase) VisitDocValues(localDocNum uint64, fields []string,
|
||||
visitor index.DocValueVisitor, dvsIn segment.DocVisitState) (
|
||||
segment.DocVisitState, error) {
|
||||
dvs, ok := dvsIn.(*docVisitState)
|
||||
if !ok || dvs == nil {
|
||||
dvs = &docVisitState{}
|
||||
} else {
|
||||
if dvs.segment != sb {
|
||||
dvs.segment = sb
|
||||
if dvs.segment != s {
|
||||
dvs.segment = s
|
||||
dvs.dvrs = nil
|
||||
dvs.bytesRead = 0
|
||||
}
|
||||
@@ -304,11 +304,11 @@ func (sb *SegmentBase) VisitDocValues(localDocNum uint64, fields []string,
|
||||
if dvs.dvrs == nil {
|
||||
dvs.dvrs = make(map[uint16]*docValueReader, len(fields))
|
||||
for _, field := range fields {
|
||||
if fieldIDPlus1, ok = sb.fieldsMap[field]; !ok {
|
||||
if fieldIDPlus1, ok = s.fieldsMap[field]; !ok {
|
||||
continue
|
||||
}
|
||||
fieldID := fieldIDPlus1 - 1
|
||||
if dvIter, exists := sb.fieldDvReaders[SectionInvertedTextIndex][fieldID]; exists &&
|
||||
if dvIter, exists := s.fieldDvReaders[SectionInvertedTextIndex][fieldID]; exists &&
|
||||
dvIter != nil {
|
||||
dvs.dvrs[fieldID] = dvIter.cloneInto(dvs.dvrs[fieldID])
|
||||
}
|
||||
@@ -324,14 +324,14 @@ func (sb *SegmentBase) VisitDocValues(localDocNum uint64, fields []string,
|
||||
docInChunk := localDocNum / chunkFactor
|
||||
var dvr *docValueReader
|
||||
for _, field := range fields {
|
||||
if fieldIDPlus1, ok = sb.fieldsMap[field]; !ok {
|
||||
if fieldIDPlus1, ok = s.fieldsMap[field]; !ok {
|
||||
continue
|
||||
}
|
||||
fieldID := fieldIDPlus1 - 1
|
||||
if dvr, ok = dvs.dvrs[fieldID]; ok && dvr != nil {
|
||||
// check if the chunk is already loaded
|
||||
if docInChunk != dvr.curChunkNumber() {
|
||||
err := dvr.loadDvChunk(docInChunk, sb)
|
||||
err := dvr.loadDvChunk(docInChunk, s)
|
||||
if err != nil {
|
||||
return dvs, err
|
||||
}
|
||||
@@ -349,6 +349,6 @@ func (sb *SegmentBase) VisitDocValues(localDocNum uint64, fields []string,
|
||||
// VisitableDocValueFields returns the list of fields with
|
||||
// persisted doc value terms ready to be visitable using the
|
||||
// VisitDocumentFieldTerms method.
|
||||
func (sb *SegmentBase) VisitableDocValueFields() ([]string, error) {
|
||||
return sb.fieldDvNames, nil
|
||||
func (s *SegmentBase) VisitableDocValueFields() ([]string, error) {
|
||||
return s.fieldDvNames, nil
|
||||
}
|
||||
|
||||
22
vendor/github.com/blevesearch/zapx/v16/faiss_vector_cache.go
generated
vendored
22
vendor/github.com/blevesearch/zapx/v16/faiss_vector_cache.go
generated
vendored
@@ -52,19 +52,31 @@ func (vc *vectorIndexCache) Clear() {
|
||||
vc.m.Unlock()
|
||||
}
|
||||
|
||||
// loadOrCreate obtains the vector index from the cache or creates it if it's not
|
||||
// present. It also returns the batch executor for the field if it's present in the
|
||||
// cache.
|
||||
// loadDocVecIDMap indicates if a non-nil docVecIDMap should be returned.
|
||||
// It is true when a filtered kNN query accesses the cache since it requires the
|
||||
// map. It's false otherwise.
|
||||
func (vc *vectorIndexCache) loadOrCreate(fieldID uint16, mem []byte,
|
||||
loadDocVecIDMap bool, except *roaring.Bitmap) (
|
||||
index *faiss.IndexImpl, vecDocIDMap map[int64]uint32, docVecIDMap map[uint32][]int64,
|
||||
vecIDsToExclude []int64, err error) {
|
||||
index, vecDocIDMap, docVecIDMap, vecIDsToExclude, err = vc.loadFromCache(
|
||||
fieldID, loadDocVecIDMap, mem, except)
|
||||
return index, vecDocIDMap, docVecIDMap, vecIDsToExclude, err
|
||||
}
|
||||
|
||||
// function to load the vectorDocIDMap and if required, docVecIDMap from cache
|
||||
// If not, it will create these and add them to the cache.
|
||||
func (vc *vectorIndexCache) loadFromCache(fieldID uint16, loadDocVecIDMap bool,
|
||||
mem []byte, except *roaring.Bitmap) (index *faiss.IndexImpl, vecDocIDMap map[int64]uint32,
|
||||
docVecIDMap map[uint32][]int64, vecIDsToExclude []int64, err error) {
|
||||
|
||||
vc.m.RLock()
|
||||
|
||||
entry, ok := vc.cache[fieldID]
|
||||
if ok {
|
||||
index, vecDocIDMap, docVecIDMap = entry.load()
|
||||
vecIDsToExclude = getVecIDsToExclude(vecDocIDMap, except)
|
||||
if !loadDocVecIDMap || len(entry.docVecIDMap) > 0 {
|
||||
if !loadDocVecIDMap || (loadDocVecIDMap && len(entry.docVecIDMap) > 0) {
|
||||
vc.m.RUnlock()
|
||||
return index, vecDocIDMap, docVecIDMap, vecIDsToExclude, nil
|
||||
}
|
||||
@@ -114,7 +126,7 @@ func (vc *vectorIndexCache) createAndCacheLOCKED(fieldID uint16, mem []byte,
|
||||
if entry != nil {
|
||||
index, vecDocIDMap, docVecIDMap = entry.load()
|
||||
vecIDsToExclude = getVecIDsToExclude(vecDocIDMap, except)
|
||||
if !loadDocVecIDMap || len(entry.docVecIDMap) > 0 {
|
||||
if !loadDocVecIDMap || (loadDocVecIDMap && len(entry.docVecIDMap) > 0) {
|
||||
return index, vecDocIDMap, docVecIDMap, vecIDsToExclude, nil
|
||||
}
|
||||
docVecIDMap = vc.addDocVecIDMapToCacheLOCKED(entry)
|
||||
|
||||
92
vendor/github.com/blevesearch/zapx/v16/faiss_vector_posting.go
generated
vendored
92
vendor/github.com/blevesearch/zapx/v16/faiss_vector_posting.go
generated
vendored
@@ -104,44 +104,44 @@ func (vpl *VecPostingsList) Iterator(prealloc segment.VecPostingsIterator) segme
|
||||
return vpl.iterator(preallocPI)
|
||||
}
|
||||
|
||||
func (vpl *VecPostingsList) iterator(rv *VecPostingsIterator) *VecPostingsIterator {
|
||||
func (p *VecPostingsList) iterator(rv *VecPostingsIterator) *VecPostingsIterator {
|
||||
if rv == nil {
|
||||
rv = &VecPostingsIterator{}
|
||||
} else {
|
||||
*rv = VecPostingsIterator{} // clear the struct
|
||||
}
|
||||
// think on some of the edge cases over here.
|
||||
if vpl.postings == nil {
|
||||
if p.postings == nil {
|
||||
return rv
|
||||
}
|
||||
rv.postings = vpl
|
||||
rv.all = vpl.postings.Iterator()
|
||||
if vpl.except != nil {
|
||||
rv.ActualBM = roaring64.AndNot(vpl.postings, vpl.except)
|
||||
rv.postings = p
|
||||
rv.all = p.postings.Iterator()
|
||||
if p.except != nil {
|
||||
rv.ActualBM = roaring64.AndNot(p.postings, p.except)
|
||||
rv.Actual = rv.ActualBM.Iterator()
|
||||
} else {
|
||||
rv.ActualBM = vpl.postings
|
||||
rv.ActualBM = p.postings
|
||||
rv.Actual = rv.all // Optimize to use same iterator for all & Actual.
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (vpl *VecPostingsList) Size() int {
|
||||
func (p *VecPostingsList) Size() int {
|
||||
sizeInBytes := reflectStaticSizeVecPostingsList + SizeOfPtr
|
||||
|
||||
if vpl.except != nil {
|
||||
sizeInBytes += int(vpl.except.GetSizeInBytes())
|
||||
if p.except != nil {
|
||||
sizeInBytes += int(p.except.GetSizeInBytes())
|
||||
}
|
||||
|
||||
return sizeInBytes
|
||||
}
|
||||
|
||||
func (vpl *VecPostingsList) Count() uint64 {
|
||||
if vpl.postings != nil {
|
||||
n := vpl.postings.GetCardinality()
|
||||
func (p *VecPostingsList) Count() uint64 {
|
||||
if p.postings != nil {
|
||||
n := p.postings.GetCardinality()
|
||||
var e uint64
|
||||
if vpl.except != nil {
|
||||
e = vpl.postings.AndCardinality(vpl.except)
|
||||
if p.except != nil {
|
||||
e = p.postings.AndCardinality(p.except)
|
||||
}
|
||||
return n - e
|
||||
}
|
||||
@@ -171,51 +171,51 @@ type VecPostingsIterator struct {
|
||||
next VecPosting // reused across Next() calls
|
||||
}
|
||||
|
||||
func (vpItr *VecPostingsIterator) nextCodeAtOrAfterClean(atOrAfter uint64) (uint64, bool, error) {
|
||||
vpItr.Actual.AdvanceIfNeeded(atOrAfter)
|
||||
func (i *VecPostingsIterator) nextCodeAtOrAfterClean(atOrAfter uint64) (uint64, bool, error) {
|
||||
i.Actual.AdvanceIfNeeded(atOrAfter)
|
||||
|
||||
if !vpItr.Actual.HasNext() {
|
||||
if !i.Actual.HasNext() {
|
||||
return 0, false, nil // couldn't find anything
|
||||
}
|
||||
|
||||
return vpItr.Actual.Next(), true, nil
|
||||
return i.Actual.Next(), true, nil
|
||||
}
|
||||
|
||||
func (vpItr *VecPostingsIterator) nextCodeAtOrAfter(atOrAfter uint64) (uint64, bool, error) {
|
||||
if vpItr.Actual == nil || !vpItr.Actual.HasNext() {
|
||||
func (i *VecPostingsIterator) nextCodeAtOrAfter(atOrAfter uint64) (uint64, bool, error) {
|
||||
if i.Actual == nil || !i.Actual.HasNext() {
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
if vpItr.postings == nil || vpItr.postings == emptyVecPostingsList {
|
||||
if i.postings == nil || i.postings == emptyVecPostingsList {
|
||||
// couldn't find anything
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
if vpItr.postings.postings == vpItr.ActualBM {
|
||||
return vpItr.nextCodeAtOrAfterClean(atOrAfter)
|
||||
if i.postings.postings == i.ActualBM {
|
||||
return i.nextCodeAtOrAfterClean(atOrAfter)
|
||||
}
|
||||
|
||||
vpItr.Actual.AdvanceIfNeeded(atOrAfter)
|
||||
i.Actual.AdvanceIfNeeded(atOrAfter)
|
||||
|
||||
if !vpItr.Actual.HasNext() || !vpItr.all.HasNext() {
|
||||
if !i.Actual.HasNext() || !i.all.HasNext() {
|
||||
// couldn't find anything
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
n := vpItr.Actual.Next()
|
||||
allN := vpItr.all.Next()
|
||||
n := i.Actual.Next()
|
||||
allN := i.all.Next()
|
||||
|
||||
// n is the next actual hit (excluding some postings), and
|
||||
// allN is the next hit in the full postings, and
|
||||
// if they don't match, move 'all' forwards until they do.
|
||||
for allN != n {
|
||||
if !vpItr.all.HasNext() {
|
||||
if !i.all.HasNext() {
|
||||
return 0, false, nil
|
||||
}
|
||||
allN = vpItr.all.Next()
|
||||
allN = i.all.Next()
|
||||
}
|
||||
|
||||
return n, true, nil
|
||||
return uint64(n), true, nil
|
||||
}
|
||||
|
||||
// a transformation function which stores both the score and the docNum as a single
|
||||
@@ -225,49 +225,49 @@ func getVectorCode(docNum uint32, score float32) uint64 {
|
||||
}
|
||||
|
||||
// Next returns the next posting on the vector postings list, or nil at the end
|
||||
func (vpItr *VecPostingsIterator) nextAtOrAfter(atOrAfter uint64) (segment.VecPosting, error) {
|
||||
func (i *VecPostingsIterator) nextAtOrAfter(atOrAfter uint64) (segment.VecPosting, error) {
|
||||
// transform the docNum provided to the vector code format and use that to
|
||||
// get the next entry. the comparison still happens docNum wise since after
|
||||
// the transformation, the docNum occupies the upper 32 bits just an entry in
|
||||
// the postings list
|
||||
atOrAfter = getVectorCode(uint32(atOrAfter), 0)
|
||||
code, exists, err := vpItr.nextCodeAtOrAfter(atOrAfter)
|
||||
code, exists, err := i.nextCodeAtOrAfter(atOrAfter)
|
||||
if err != nil || !exists {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vpItr.next = VecPosting{} // clear the struct
|
||||
rv := &vpItr.next
|
||||
i.next = VecPosting{} // clear the struct
|
||||
rv := &i.next
|
||||
rv.score = math.Float32frombits(uint32(code))
|
||||
rv.docNum = code >> 32
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (vpItr *VecPostingsIterator) Next() (segment.VecPosting, error) {
|
||||
return vpItr.nextAtOrAfter(0)
|
||||
func (itr *VecPostingsIterator) Next() (segment.VecPosting, error) {
|
||||
return itr.nextAtOrAfter(0)
|
||||
}
|
||||
|
||||
func (vpItr *VecPostingsIterator) Advance(docNum uint64) (segment.VecPosting, error) {
|
||||
return vpItr.nextAtOrAfter(docNum)
|
||||
func (itr *VecPostingsIterator) Advance(docNum uint64) (segment.VecPosting, error) {
|
||||
return itr.nextAtOrAfter(docNum)
|
||||
}
|
||||
|
||||
func (vpItr *VecPostingsIterator) Size() int {
|
||||
func (i *VecPostingsIterator) Size() int {
|
||||
sizeInBytes := reflectStaticSizePostingsIterator + SizeOfPtr +
|
||||
vpItr.next.Size()
|
||||
i.next.Size()
|
||||
|
||||
return sizeInBytes
|
||||
}
|
||||
|
||||
func (vpItr *VecPostingsIterator) ResetBytesRead(val uint64) {
|
||||
func (vpl *VecPostingsIterator) ResetBytesRead(val uint64) {
|
||||
|
||||
}
|
||||
|
||||
func (vpItr *VecPostingsIterator) BytesRead() uint64 {
|
||||
func (vpl *VecPostingsIterator) BytesRead() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (vpItr *VecPostingsIterator) BytesWritten() uint64 {
|
||||
func (vpl *VecPostingsIterator) BytesWritten() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ func (sb *SegmentBase) InterpretVectorIndex(field string, requiresFiltering bool
|
||||
// it isn't added to the final postings list.
|
||||
if docID, ok := vecDocIDMap[vecID]; ok {
|
||||
code := getVectorCode(docID, scores[i])
|
||||
pl.postings.Add(code)
|
||||
pl.postings.Add(uint64(code))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -471,7 +471,7 @@ func (sb *SegmentBase) InterpretVectorIndex(field string, requiresFiltering bool
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If no error occurred during the creation of the selector, then
|
||||
// If no error occured during the creation of the selector, then
|
||||
// it should be deleted once the search is complete.
|
||||
defer selector.Delete()
|
||||
// Ordering the retrieved centroid IDs by increasing order
|
||||
|
||||
12
vendor/github.com/blevesearch/zapx/v16/merge.go
generated
vendored
12
vendor/github.com/blevesearch/zapx/v16/merge.go
generated
vendored
@@ -537,21 +537,21 @@ func mergeStoredAndRemap(segments []*SegmentBase, drops []*roaring.Bitmap,
|
||||
// copyStoredDocs writes out a segment's stored doc info, optimized by
|
||||
// using a single Write() call for the entire set of bytes. The
|
||||
// newDocNumOffsets is filled with the new offsets for each doc.
|
||||
func (sb *SegmentBase) copyStoredDocs(newDocNum uint64, newDocNumOffsets []uint64,
|
||||
func (s *SegmentBase) copyStoredDocs(newDocNum uint64, newDocNumOffsets []uint64,
|
||||
w *CountHashWriter) error {
|
||||
if sb.numDocs <= 0 {
|
||||
if s.numDocs <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
indexOffset0, storedOffset0, _, _, _ :=
|
||||
sb.getDocStoredOffsets(0) // the segment's first doc
|
||||
s.getDocStoredOffsets(0) // the segment's first doc
|
||||
|
||||
indexOffsetN, storedOffsetN, readN, metaLenN, dataLenN :=
|
||||
sb.getDocStoredOffsets(sb.numDocs - 1) // the segment's last doc
|
||||
s.getDocStoredOffsets(s.numDocs - 1) // the segment's last doc
|
||||
|
||||
storedOffset0New := uint64(w.Count())
|
||||
|
||||
storedBytes := sb.mem[storedOffset0 : storedOffsetN+readN+metaLenN+dataLenN]
|
||||
storedBytes := s.mem[storedOffset0 : storedOffsetN+readN+metaLenN+dataLenN]
|
||||
_, err := w.Write(storedBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -560,7 +560,7 @@ func (sb *SegmentBase) copyStoredDocs(newDocNum uint64, newDocNumOffsets []uint6
|
||||
// remap the storedOffset's for the docs into new offsets relative
|
||||
// to storedOffset0New, filling the given docNumOffsetsOut array
|
||||
for indexOffset := indexOffset0; indexOffset <= indexOffsetN; indexOffset += 8 {
|
||||
storedOffset := binary.BigEndian.Uint64(sb.mem[indexOffset : indexOffset+8])
|
||||
storedOffset := binary.BigEndian.Uint64(s.mem[indexOffset : indexOffset+8])
|
||||
storedOffsetNew := storedOffset - storedOffset0 + storedOffset0New
|
||||
newDocNumOffsets[newDocNum] = storedOffsetNew
|
||||
newDocNum += 1
|
||||
|
||||
18
vendor/github.com/blevesearch/zapx/v16/read.go
generated
vendored
18
vendor/github.com/blevesearch/zapx/v16/read.go
generated
vendored
@@ -16,27 +16,27 @@ package zap
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
func (sb *SegmentBase) getDocStoredMetaAndCompressed(docNum uint64) ([]byte, []byte) {
|
||||
_, storedOffset, n, metaLen, dataLen := sb.getDocStoredOffsets(docNum)
|
||||
func (s *SegmentBase) getDocStoredMetaAndCompressed(docNum uint64) ([]byte, []byte) {
|
||||
_, storedOffset, n, metaLen, dataLen := s.getDocStoredOffsets(docNum)
|
||||
|
||||
meta := sb.mem[storedOffset+n : storedOffset+n+metaLen]
|
||||
data := sb.mem[storedOffset+n+metaLen : storedOffset+n+metaLen+dataLen]
|
||||
meta := s.mem[storedOffset+n : storedOffset+n+metaLen]
|
||||
data := s.mem[storedOffset+n+metaLen : storedOffset+n+metaLen+dataLen]
|
||||
|
||||
return meta, data
|
||||
}
|
||||
|
||||
func (sb *SegmentBase) getDocStoredOffsets(docNum uint64) (
|
||||
func (s *SegmentBase) getDocStoredOffsets(docNum uint64) (
|
||||
uint64, uint64, uint64, uint64, uint64) {
|
||||
indexOffset := sb.storedIndexOffset + (8 * docNum)
|
||||
indexOffset := s.storedIndexOffset + (8 * docNum)
|
||||
|
||||
storedOffset := binary.BigEndian.Uint64(sb.mem[indexOffset : indexOffset+8])
|
||||
storedOffset := binary.BigEndian.Uint64(s.mem[indexOffset : indexOffset+8])
|
||||
|
||||
var n uint64
|
||||
|
||||
metaLen, read := binary.Uvarint(sb.mem[storedOffset : storedOffset+binary.MaxVarintLen64])
|
||||
metaLen, read := binary.Uvarint(s.mem[storedOffset : storedOffset+binary.MaxVarintLen64])
|
||||
n += uint64(read)
|
||||
|
||||
dataLen, read := binary.Uvarint(sb.mem[storedOffset+n : storedOffset+n+binary.MaxVarintLen64])
|
||||
dataLen, read := binary.Uvarint(s.mem[storedOffset+n : storedOffset+n+binary.MaxVarintLen64])
|
||||
n += uint64(read)
|
||||
|
||||
return indexOffset, storedOffset, n, metaLen, dataLen
|
||||
|
||||
16
vendor/github.com/blevesearch/zapx/v16/section_inverted_text_index.go
generated
vendored
16
vendor/github.com/blevesearch/zapx/v16/section_inverted_text_index.go
generated
vendored
@@ -612,10 +612,8 @@ func (io *invertedIndexOpaque) writeDicts(w *CountHashWriter) (dictOffsets []uin
|
||||
if io.IncludeDocValues[fieldID] {
|
||||
for docNum, docTerms := range docTermMap {
|
||||
if fieldTermMap, ok := io.extraDocValues[docNum]; ok {
|
||||
if sTerms, ok := fieldTermMap[uint16(fieldID)]; ok {
|
||||
for _, sTerm := range sTerms {
|
||||
docTerms = append(append(docTerms, sTerm...), termSeparator)
|
||||
}
|
||||
if sTerm, ok := fieldTermMap[uint16(fieldID)]; ok {
|
||||
docTerms = append(append(docTerms, sTerm...), termSeparator)
|
||||
}
|
||||
}
|
||||
if len(docTerms) > 0 {
|
||||
@@ -799,9 +797,9 @@ func (i *invertedIndexOpaque) realloc() {
|
||||
|
||||
if f, ok := field.(index.GeoShapeField); ok {
|
||||
if _, exists := i.extraDocValues[docNum]; !exists {
|
||||
i.extraDocValues[docNum] = make(map[uint16][][]byte)
|
||||
i.extraDocValues[docNum] = make(map[uint16][]byte)
|
||||
}
|
||||
i.extraDocValues[docNum][fieldID] = append(i.extraDocValues[docNum][fieldID], f.EncodedShape())
|
||||
i.extraDocValues[docNum][fieldID] = f.EncodedShape()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -812,7 +810,7 @@ func (i *invertedIndexOpaque) realloc() {
|
||||
}
|
||||
|
||||
if i.extraDocValues == nil {
|
||||
i.extraDocValues = map[int]map[uint16][][]byte{}
|
||||
i.extraDocValues = map[int]map[uint16][]byte{}
|
||||
}
|
||||
|
||||
for docNum, result := range i.results {
|
||||
@@ -980,8 +978,8 @@ type invertedIndexOpaque struct {
|
||||
|
||||
// store terms that are unnecessary for the term dictionaries but needed in doc values
|
||||
// eg - encoded geoshapes
|
||||
// docNum -> fieldID -> terms
|
||||
extraDocValues map[int]map[uint16][][]byte
|
||||
// docNum -> fieldID -> term
|
||||
extraDocValues map[int]map[uint16][]byte
|
||||
|
||||
builder *vellum.Builder
|
||||
builderBuf bytes.Buffer
|
||||
|
||||
158
vendor/github.com/blevesearch/zapx/v16/segment.go
generated
vendored
158
vendor/github.com/blevesearch/zapx/v16/segment.go
generated
vendored
@@ -269,81 +269,81 @@ func (s *Segment) incrementBytesRead(val uint64) {
|
||||
atomic.AddUint64(&s.bytesRead, val)
|
||||
}
|
||||
|
||||
func (sb *SegmentBase) BytesWritten() uint64 {
|
||||
return atomic.LoadUint64(&sb.bytesWritten)
|
||||
func (s *SegmentBase) BytesWritten() uint64 {
|
||||
return atomic.LoadUint64(&s.bytesWritten)
|
||||
}
|
||||
|
||||
func (sb *SegmentBase) setBytesWritten(val uint64) {
|
||||
atomic.AddUint64(&sb.bytesWritten, val)
|
||||
func (s *SegmentBase) setBytesWritten(val uint64) {
|
||||
atomic.AddUint64(&s.bytesWritten, val)
|
||||
}
|
||||
|
||||
func (sb *SegmentBase) BytesRead() uint64 {
|
||||
func (s *SegmentBase) BytesRead() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (sb *SegmentBase) ResetBytesRead(val uint64) {}
|
||||
func (s *SegmentBase) ResetBytesRead(val uint64) {}
|
||||
|
||||
func (sb *SegmentBase) incrementBytesRead(val uint64) {
|
||||
atomic.AddUint64(&sb.bytesRead, val)
|
||||
func (s *SegmentBase) incrementBytesRead(val uint64) {
|
||||
atomic.AddUint64(&s.bytesRead, val)
|
||||
}
|
||||
|
||||
func (sb *SegmentBase) loadFields() error {
|
||||
func (s *SegmentBase) loadFields() error {
|
||||
// NOTE for now we assume the fields index immediately precedes
|
||||
// the footer, and if this changes, need to adjust accordingly (or
|
||||
// store explicit length), where s.mem was sliced from s.mm in Open().
|
||||
fieldsIndexEnd := uint64(len(sb.mem))
|
||||
fieldsIndexEnd := uint64(len(s.mem))
|
||||
|
||||
// iterate through fields index
|
||||
var fieldID uint64
|
||||
for sb.fieldsIndexOffset+(8*fieldID) < fieldsIndexEnd {
|
||||
addr := binary.BigEndian.Uint64(sb.mem[sb.fieldsIndexOffset+(8*fieldID) : sb.fieldsIndexOffset+(8*fieldID)+8])
|
||||
for s.fieldsIndexOffset+(8*fieldID) < fieldsIndexEnd {
|
||||
addr := binary.BigEndian.Uint64(s.mem[s.fieldsIndexOffset+(8*fieldID) : s.fieldsIndexOffset+(8*fieldID)+8])
|
||||
|
||||
// accounting the address of the dictLoc being read from file
|
||||
sb.incrementBytesRead(8)
|
||||
s.incrementBytesRead(8)
|
||||
|
||||
dictLoc, read := binary.Uvarint(sb.mem[addr:fieldsIndexEnd])
|
||||
dictLoc, read := binary.Uvarint(s.mem[addr:fieldsIndexEnd])
|
||||
n := uint64(read)
|
||||
sb.dictLocs = append(sb.dictLocs, dictLoc)
|
||||
s.dictLocs = append(s.dictLocs, dictLoc)
|
||||
|
||||
var nameLen uint64
|
||||
nameLen, read = binary.Uvarint(sb.mem[addr+n : fieldsIndexEnd])
|
||||
nameLen, read = binary.Uvarint(s.mem[addr+n : fieldsIndexEnd])
|
||||
n += uint64(read)
|
||||
|
||||
name := string(sb.mem[addr+n : addr+n+nameLen])
|
||||
name := string(s.mem[addr+n : addr+n+nameLen])
|
||||
|
||||
sb.incrementBytesRead(n + nameLen)
|
||||
sb.fieldsInv = append(sb.fieldsInv, name)
|
||||
sb.fieldsMap[name] = uint16(fieldID + 1)
|
||||
s.incrementBytesRead(n + nameLen)
|
||||
s.fieldsInv = append(s.fieldsInv, name)
|
||||
s.fieldsMap[name] = uint16(fieldID + 1)
|
||||
|
||||
fieldID++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sb *SegmentBase) loadFieldsNew() error {
|
||||
pos := sb.sectionsIndexOffset
|
||||
func (s *SegmentBase) loadFieldsNew() error {
|
||||
pos := s.sectionsIndexOffset
|
||||
|
||||
if pos == 0 {
|
||||
// this is the case only for older file formats
|
||||
return sb.loadFields()
|
||||
return s.loadFields()
|
||||
}
|
||||
|
||||
seek := pos + binary.MaxVarintLen64
|
||||
if seek > uint64(len(sb.mem)) {
|
||||
if seek > uint64(len(s.mem)) {
|
||||
// handling a buffer overflow case.
|
||||
// a rare case where the backing buffer is not large enough to be read directly via
|
||||
// a pos+binary.MaxVarintLen64 seek. For eg, this can happen when there is only
|
||||
// one field to be indexed in the entire batch of data and while writing out
|
||||
// these fields metadata, you write 1 + 8 bytes whereas the MaxVarintLen64 = 10.
|
||||
seek = uint64(len(sb.mem))
|
||||
seek = uint64(len(s.mem))
|
||||
}
|
||||
|
||||
// read the number of fields
|
||||
numFields, sz := binary.Uvarint(sb.mem[pos:seek])
|
||||
numFields, sz := binary.Uvarint(s.mem[pos:seek])
|
||||
// here, the pos is incremented by the valid number bytes read from the buffer
|
||||
// so in the edge case pointed out above the numFields = 1, the sz = 1 as well.
|
||||
pos += uint64(sz)
|
||||
sb.incrementBytesRead(uint64(sz))
|
||||
s.incrementBytesRead(uint64(sz))
|
||||
|
||||
// the following loop will be executed only once in the edge case pointed out above
|
||||
// since there is only field's offset store which occupies 8 bytes.
|
||||
@@ -352,17 +352,17 @@ func (sb *SegmentBase) loadFieldsNew() error {
|
||||
// the specific section's parsing logic.
|
||||
var fieldID uint64
|
||||
for fieldID < numFields {
|
||||
addr := binary.BigEndian.Uint64(sb.mem[pos : pos+8])
|
||||
sb.incrementBytesRead(8)
|
||||
addr := binary.BigEndian.Uint64(s.mem[pos : pos+8])
|
||||
s.incrementBytesRead(8)
|
||||
|
||||
fieldSectionMap := make(map[uint16]uint64)
|
||||
|
||||
err := sb.loadFieldNew(uint16(fieldID), addr, fieldSectionMap)
|
||||
err := s.loadFieldNew(uint16(fieldID), addr, fieldSectionMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sb.fieldsSectionsMap = append(sb.fieldsSectionsMap, fieldSectionMap)
|
||||
s.fieldsSectionsMap = append(s.fieldsSectionsMap, fieldSectionMap)
|
||||
|
||||
fieldID++
|
||||
pos += 8
|
||||
@@ -371,7 +371,7 @@ func (sb *SegmentBase) loadFieldsNew() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sb *SegmentBase) loadFieldNew(fieldID uint16, pos uint64,
|
||||
func (s *SegmentBase) loadFieldNew(fieldID uint16, pos uint64,
|
||||
fieldSectionMap map[uint16]uint64) error {
|
||||
if pos == 0 {
|
||||
// there is no indexing structure present for this field/section
|
||||
@@ -379,23 +379,23 @@ func (sb *SegmentBase) loadFieldNew(fieldID uint16, pos uint64,
|
||||
}
|
||||
|
||||
fieldStartPos := pos // to track the number of bytes read
|
||||
fieldNameLen, sz := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
||||
fieldNameLen, sz := binary.Uvarint(s.mem[pos : pos+binary.MaxVarintLen64])
|
||||
pos += uint64(sz)
|
||||
|
||||
fieldName := string(sb.mem[pos : pos+fieldNameLen])
|
||||
fieldName := string(s.mem[pos : pos+fieldNameLen])
|
||||
pos += fieldNameLen
|
||||
|
||||
sb.fieldsInv = append(sb.fieldsInv, fieldName)
|
||||
sb.fieldsMap[fieldName] = uint16(fieldID + 1)
|
||||
s.fieldsInv = append(s.fieldsInv, fieldName)
|
||||
s.fieldsMap[fieldName] = uint16(fieldID + 1)
|
||||
|
||||
fieldNumSections, sz := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
||||
fieldNumSections, sz := binary.Uvarint(s.mem[pos : pos+binary.MaxVarintLen64])
|
||||
pos += uint64(sz)
|
||||
|
||||
for sectionIdx := uint64(0); sectionIdx < fieldNumSections; sectionIdx++ {
|
||||
// read section id
|
||||
fieldSectionType := binary.BigEndian.Uint16(sb.mem[pos : pos+2])
|
||||
fieldSectionType := binary.BigEndian.Uint16(s.mem[pos : pos+2])
|
||||
pos += 2
|
||||
fieldSectionAddr := binary.BigEndian.Uint64(sb.mem[pos : pos+8])
|
||||
fieldSectionAddr := binary.BigEndian.Uint64(s.mem[pos : pos+8])
|
||||
pos += 8
|
||||
fieldSectionMap[fieldSectionType] = fieldSectionAddr
|
||||
if fieldSectionType == SectionInvertedTextIndex {
|
||||
@@ -403,33 +403,33 @@ func (sb *SegmentBase) loadFieldNew(fieldID uint16, pos uint64,
|
||||
// 0 and during query time, because there is no valid dictionary we
|
||||
// will just have follow a no-op path.
|
||||
if fieldSectionAddr == 0 {
|
||||
sb.dictLocs = append(sb.dictLocs, 0)
|
||||
s.dictLocs = append(s.dictLocs, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
read := 0
|
||||
// skip the doc values
|
||||
_, n := binary.Uvarint(sb.mem[fieldSectionAddr : fieldSectionAddr+binary.MaxVarintLen64])
|
||||
_, n := binary.Uvarint(s.mem[fieldSectionAddr : fieldSectionAddr+binary.MaxVarintLen64])
|
||||
fieldSectionAddr += uint64(n)
|
||||
read += n
|
||||
_, n = binary.Uvarint(sb.mem[fieldSectionAddr : fieldSectionAddr+binary.MaxVarintLen64])
|
||||
_, n = binary.Uvarint(s.mem[fieldSectionAddr : fieldSectionAddr+binary.MaxVarintLen64])
|
||||
fieldSectionAddr += uint64(n)
|
||||
read += n
|
||||
dictLoc, n := binary.Uvarint(sb.mem[fieldSectionAddr : fieldSectionAddr+binary.MaxVarintLen64])
|
||||
dictLoc, n := binary.Uvarint(s.mem[fieldSectionAddr : fieldSectionAddr+binary.MaxVarintLen64])
|
||||
// account the bytes read while parsing the field's inverted index section
|
||||
sb.incrementBytesRead(uint64(read + n))
|
||||
sb.dictLocs = append(sb.dictLocs, dictLoc)
|
||||
s.incrementBytesRead(uint64(read + n))
|
||||
s.dictLocs = append(s.dictLocs, dictLoc)
|
||||
}
|
||||
}
|
||||
|
||||
// account the bytes read while parsing the sections field index.
|
||||
sb.incrementBytesRead((pos - uint64(fieldStartPos)) + fieldNameLen)
|
||||
s.incrementBytesRead((pos - uint64(fieldStartPos)) + fieldNameLen)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dictionary returns the term dictionary for the specified field
|
||||
func (sb *SegmentBase) Dictionary(field string) (segment.TermDictionary, error) {
|
||||
dict, err := sb.dictionary(field)
|
||||
func (s *SegmentBase) Dictionary(field string) (segment.TermDictionary, error) {
|
||||
dict, err := s.dictionary(field)
|
||||
if err == nil && dict == nil {
|
||||
return emptyDictionary, nil
|
||||
}
|
||||
@@ -479,8 +479,8 @@ func (sb *SegmentBase) dictionary(field string) (rv *Dictionary, err error) {
|
||||
}
|
||||
|
||||
// Thesaurus returns the thesaurus with the specified name, or an empty thesaurus if not found.
|
||||
func (sb *SegmentBase) Thesaurus(name string) (segment.Thesaurus, error) {
|
||||
thesaurus, err := sb.thesaurus(name)
|
||||
func (s *SegmentBase) Thesaurus(name string) (segment.Thesaurus, error) {
|
||||
thesaurus, err := s.thesaurus(name)
|
||||
if err == nil && thesaurus == nil {
|
||||
return emptyThesaurus, nil
|
||||
}
|
||||
@@ -537,17 +537,17 @@ var visitDocumentCtxPool = sync.Pool{
|
||||
|
||||
// VisitStoredFields invokes the StoredFieldValueVisitor for each stored field
|
||||
// for the specified doc number
|
||||
func (sb *SegmentBase) VisitStoredFields(num uint64, visitor segment.StoredFieldValueVisitor) error {
|
||||
func (s *SegmentBase) VisitStoredFields(num uint64, visitor segment.StoredFieldValueVisitor) error {
|
||||
vdc := visitDocumentCtxPool.Get().(*visitDocumentCtx)
|
||||
defer visitDocumentCtxPool.Put(vdc)
|
||||
return sb.visitStoredFields(vdc, num, visitor)
|
||||
return s.visitStoredFields(vdc, num, visitor)
|
||||
}
|
||||
|
||||
func (sb *SegmentBase) visitStoredFields(vdc *visitDocumentCtx, num uint64,
|
||||
func (s *SegmentBase) visitStoredFields(vdc *visitDocumentCtx, num uint64,
|
||||
visitor segment.StoredFieldValueVisitor) error {
|
||||
// first make sure this is a valid number in this segment
|
||||
if num < sb.numDocs {
|
||||
meta, compressed := sb.getDocStoredMetaAndCompressed(num)
|
||||
if num < s.numDocs {
|
||||
meta, compressed := s.getDocStoredMetaAndCompressed(num)
|
||||
|
||||
vdc.reader.Reset(meta)
|
||||
|
||||
@@ -611,7 +611,7 @@ func (sb *SegmentBase) visitStoredFields(vdc *visitDocumentCtx, num uint64,
|
||||
}
|
||||
}
|
||||
value := uncompressed[offset : offset+l]
|
||||
keepGoing = visitor(sb.fieldsInv[field], byte(typ), value, arrayPos)
|
||||
keepGoing = visitor(s.fieldsInv[field], byte(typ), value, arrayPos)
|
||||
}
|
||||
|
||||
vdc.buf = uncompressed
|
||||
@@ -620,14 +620,14 @@ func (sb *SegmentBase) visitStoredFields(vdc *visitDocumentCtx, num uint64,
|
||||
}
|
||||
|
||||
// DocID returns the value of the _id field for the given docNum
|
||||
func (sb *SegmentBase) DocID(num uint64) ([]byte, error) {
|
||||
if num >= sb.numDocs {
|
||||
func (s *SegmentBase) DocID(num uint64) ([]byte, error) {
|
||||
if num >= s.numDocs {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
vdc := visitDocumentCtxPool.Get().(*visitDocumentCtx)
|
||||
|
||||
meta, compressed := sb.getDocStoredMetaAndCompressed(num)
|
||||
meta, compressed := s.getDocStoredMetaAndCompressed(num)
|
||||
|
||||
vdc.reader.Reset(meta)
|
||||
|
||||
@@ -644,17 +644,17 @@ func (sb *SegmentBase) DocID(num uint64) ([]byte, error) {
|
||||
}
|
||||
|
||||
// Count returns the number of documents in this segment.
|
||||
func (sb *SegmentBase) Count() uint64 {
|
||||
return sb.numDocs
|
||||
func (s *SegmentBase) Count() uint64 {
|
||||
return s.numDocs
|
||||
}
|
||||
|
||||
// DocNumbers returns a bitset corresponding to the doc numbers of all the
|
||||
// provided _id strings
|
||||
func (sb *SegmentBase) DocNumbers(ids []string) (*roaring.Bitmap, error) {
|
||||
func (s *SegmentBase) DocNumbers(ids []string) (*roaring.Bitmap, error) {
|
||||
rv := roaring.New()
|
||||
|
||||
if len(sb.fieldsMap) > 0 {
|
||||
idDict, err := sb.dictionary("_id")
|
||||
if len(s.fieldsMap) > 0 {
|
||||
idDict, err := s.dictionary("_id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -681,8 +681,8 @@ func (sb *SegmentBase) DocNumbers(ids []string) (*roaring.Bitmap, error) {
|
||||
}
|
||||
|
||||
// Fields returns the field names used in this segment
|
||||
func (sb *SegmentBase) Fields() []string {
|
||||
return sb.fieldsInv
|
||||
func (s *SegmentBase) Fields() []string {
|
||||
return s.fieldsInv
|
||||
}
|
||||
|
||||
// Path returns the path of this segment on disk
|
||||
@@ -907,44 +907,44 @@ func (s *Segment) loadDvReaders() error {
|
||||
// since segmentBase is an in-memory segment, it can be called only
|
||||
// for v16 file formats as part of InitSegmentBase() while introducing
|
||||
// a segment into the system.
|
||||
func (sb *SegmentBase) loadDvReaders() error {
|
||||
func (s *SegmentBase) loadDvReaders() error {
|
||||
|
||||
// evaluate -> s.docValueOffset == fieldNotUninverted
|
||||
if sb.numDocs == 0 {
|
||||
if s.numDocs == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for fieldID, sections := range sb.fieldsSectionsMap {
|
||||
for fieldID, sections := range s.fieldsSectionsMap {
|
||||
for secID, secOffset := range sections {
|
||||
if secOffset > 0 {
|
||||
// fixed encoding as of now, need to uvarint this
|
||||
pos := secOffset
|
||||
var read uint64
|
||||
fieldLocStart, n := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
||||
fieldLocStart, n := binary.Uvarint(s.mem[pos : pos+binary.MaxVarintLen64])
|
||||
if n <= 0 {
|
||||
return fmt.Errorf("loadDvReaders: failed to read the docvalue offset start for field %v", sb.fieldsInv[fieldID])
|
||||
return fmt.Errorf("loadDvReaders: failed to read the docvalue offset start for field %v", s.fieldsInv[fieldID])
|
||||
}
|
||||
pos += uint64(n)
|
||||
read += uint64(n)
|
||||
fieldLocEnd, n := binary.Uvarint(sb.mem[pos : pos+binary.MaxVarintLen64])
|
||||
fieldLocEnd, n := binary.Uvarint(s.mem[pos : pos+binary.MaxVarintLen64])
|
||||
if read <= 0 {
|
||||
return fmt.Errorf("loadDvReaders: failed to read the docvalue offset end for field %v", sb.fieldsInv[fieldID])
|
||||
return fmt.Errorf("loadDvReaders: failed to read the docvalue offset end for field %v", s.fieldsInv[fieldID])
|
||||
}
|
||||
pos += uint64(n)
|
||||
read += uint64(n)
|
||||
|
||||
sb.incrementBytesRead(read)
|
||||
s.incrementBytesRead(read)
|
||||
|
||||
fieldDvReader, err := sb.loadFieldDocValueReader(sb.fieldsInv[fieldID], fieldLocStart, fieldLocEnd)
|
||||
fieldDvReader, err := s.loadFieldDocValueReader(s.fieldsInv[fieldID], fieldLocStart, fieldLocEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fieldDvReader != nil {
|
||||
if sb.fieldDvReaders[secID] == nil {
|
||||
sb.fieldDvReaders[secID] = make(map[uint16]*docValueReader)
|
||||
if s.fieldDvReaders[secID] == nil {
|
||||
s.fieldDvReaders[secID] = make(map[uint16]*docValueReader)
|
||||
}
|
||||
sb.fieldDvReaders[secID][uint16(fieldID)] = fieldDvReader
|
||||
sb.fieldDvNames = append(sb.fieldDvNames, sb.fieldsInv[fieldID])
|
||||
s.fieldDvReaders[secID][uint16(fieldID)] = fieldDvReader
|
||||
s.fieldDvNames = append(s.fieldDvNames, s.fieldsInv[fieldID])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/github.com/coreos/go-semver/semver/semver.go
generated
vendored
2
vendor/github.com/coreos/go-semver/semver/semver.go
generated
vendored
@@ -85,7 +85,7 @@ func (v *Version) Set(version string) error {
|
||||
return fmt.Errorf("failed to validate metadata: %v", err)
|
||||
}
|
||||
|
||||
parsed := make([]int64, 3)
|
||||
parsed := make([]int64, 3, 3)
|
||||
|
||||
for i, v := range dotParts[:3] {
|
||||
val, err := strconv.ParseInt(v, 10, 64)
|
||||
|
||||
2
vendor/github.com/fsnotify/fsnotify/.cirrus.yml
generated
vendored
2
vendor/github.com/fsnotify/fsnotify/.cirrus.yml
generated
vendored
@@ -1,7 +1,7 @@
|
||||
freebsd_task:
|
||||
name: 'FreeBSD'
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-2
|
||||
image_family: freebsd-14-1
|
||||
install_script:
|
||||
- pkg update -f
|
||||
- pkg install -y go
|
||||
|
||||
35
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
35
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
@@ -1,39 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
1.9.0 2024-04-04
|
||||
----------------
|
||||
|
||||
### Changes and fixes
|
||||
|
||||
- all: make BufferedWatcher buffered again ([#657])
|
||||
|
||||
- inotify: fix race when adding/removing watches while a watched path is being
|
||||
deleted ([#678], [#686])
|
||||
|
||||
- inotify: don't send empty event if a watched path is unmounted ([#655])
|
||||
|
||||
- inotify: don't register duplicate watches when watching both a symlink and its
|
||||
target; previously that would get "half-added" and removing the second would
|
||||
panic ([#679])
|
||||
|
||||
- kqueue: fix watching relative symlinks ([#681])
|
||||
|
||||
- kqueue: correctly mark pre-existing entries when watching a link to a dir on
|
||||
kqueue ([#682])
|
||||
|
||||
- illumos: don't send error if changed file is deleted while processing the
|
||||
event ([#678])
|
||||
|
||||
|
||||
[#657]: https://github.com/fsnotify/fsnotify/pull/657
|
||||
[#678]: https://github.com/fsnotify/fsnotify/pull/678
|
||||
[#686]: https://github.com/fsnotify/fsnotify/pull/686
|
||||
[#655]: https://github.com/fsnotify/fsnotify/pull/655
|
||||
[#681]: https://github.com/fsnotify/fsnotify/pull/681
|
||||
[#679]: https://github.com/fsnotify/fsnotify/pull/679
|
||||
[#682]: https://github.com/fsnotify/fsnotify/pull/682
|
||||
|
||||
1.8.0 2024-10-31
|
||||
1.8.0 2023-10-31
|
||||
----------------
|
||||
|
||||
### Additions
|
||||
|
||||
1
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
1
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
@@ -77,7 +77,6 @@ End-of-line escapes with `\` are not supported.
|
||||
debug [yes/no] # Enable/disable FSNOTIFY_DEBUG (tests are run in
|
||||
parallel by default, so -parallel=1 is probably a good
|
||||
idea).
|
||||
print [any strings] # Print text to stdout; for debugging.
|
||||
|
||||
touch path
|
||||
mkdir [-p] dir
|
||||
|
||||
2
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
2
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
@@ -15,6 +15,7 @@ Platform support:
|
||||
| ReadDirectoryChangesW | Windows | Supported |
|
||||
| FEN | illumos | Supported |
|
||||
| fanotify | Linux 5.9+ | [Not yet](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||
| AHAFS | AIX | [aix branch]; experimental due to lack of maintainer and test environment |
|
||||
| FSEvents | macOS | [Needs support in x/sys/unix][fsevents] |
|
||||
| USN Journals | Windows | [Needs support in x/sys/windows][usn] |
|
||||
| Polling | *All* | [Not yet](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||
@@ -24,6 +25,7 @@ untested.
|
||||
|
||||
[fsevents]: https://github.com/fsnotify/fsnotify/issues/11#issuecomment-1279133120
|
||||
[usn]: https://github.com/fsnotify/fsnotify/issues/53#issuecomment-1279829847
|
||||
[aix branch]: https://github.com/fsnotify/fsnotify/issues/353#issuecomment-1284590129
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
107
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
107
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
@@ -9,7 +9,6 @@ package fsnotify
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@@ -20,25 +19,27 @@ import (
|
||||
)
|
||||
|
||||
type fen struct {
|
||||
*shared
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
|
||||
mu sync.Mutex
|
||||
port *unix.EventPort
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
dirs map[string]Op // Explicitly watched directories
|
||||
watches map[string]Op // Explicitly watched non-directories
|
||||
}
|
||||
|
||||
var defaultBufferSize = 0
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return newBufferedBackend(0, ev, errs)
|
||||
}
|
||||
|
||||
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
|
||||
w := &fen{
|
||||
shared: newShared(ev, errs),
|
||||
Events: ev,
|
||||
Errors: errs,
|
||||
dirs: make(map[string]Op),
|
||||
watches: make(map[string]Op),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -51,10 +52,49 @@ func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// sendEvent attempts to send an event to the user, returning true if the event
|
||||
// was put in the channel successfully and false if the watcher has been closed.
|
||||
func (w *fen) sendEvent(name string, op Op) (sent bool) {
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Events <- Event{Name: name, Op: op}:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// sendError attempts to send an error to the user, returning true if the error
|
||||
// was put in the channel successfully and false if the watcher has been closed.
|
||||
func (w *fen) sendError(err error) (sent bool) {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *fen) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *fen) Close() error {
|
||||
if w.shared.close() {
|
||||
// Take the lock used by associateFile to prevent lingering events from
|
||||
// being processed after the close
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if w.isClosed() {
|
||||
return nil
|
||||
}
|
||||
close(w.done)
|
||||
return w.port.Close()
|
||||
}
|
||||
|
||||
@@ -169,7 +209,7 @@ func (w *fen) readEvents() {
|
||||
return
|
||||
}
|
||||
// There was an error not caused by calling w.Close()
|
||||
if !w.sendError(fmt.Errorf("port.Get: %w", err)) {
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -237,13 +277,13 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
||||
isWatched := watchedDir || watchedPath
|
||||
|
||||
if events&unix.FILE_DELETE != 0 {
|
||||
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||
if !w.sendEvent(path, Remove) {
|
||||
return nil
|
||||
}
|
||||
reRegister = false
|
||||
}
|
||||
if events&unix.FILE_RENAME_FROM != 0 {
|
||||
if !w.sendEvent(Event{Name: path, Op: Rename}) {
|
||||
if !w.sendEvent(path, Rename) {
|
||||
return nil
|
||||
}
|
||||
// Don't keep watching the new file name
|
||||
@@ -257,7 +297,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
||||
|
||||
// inotify reports a Remove event in this case, so we simulate this
|
||||
// here.
|
||||
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||
if !w.sendEvent(path, Remove) {
|
||||
return nil
|
||||
}
|
||||
// Don't keep watching the file that was removed
|
||||
@@ -291,7 +331,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
||||
// get here, the sudirectory is already gone. Clearly we were watching
|
||||
// this path but now it is gone. Let's tell the user that it was
|
||||
// removed.
|
||||
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||
if !w.sendEvent(path, Remove) {
|
||||
return nil
|
||||
}
|
||||
// Suppress extra write events on removed directories; they are not
|
||||
@@ -306,7 +346,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
||||
if err != nil {
|
||||
// The symlink still exists, but the target is gone. Report the
|
||||
// Remove similar to above.
|
||||
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||
if !w.sendEvent(path, Remove) {
|
||||
return nil
|
||||
}
|
||||
// Don't return the error
|
||||
@@ -319,7 +359,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if !w.sendEvent(Event{Name: path, Op: Write}) {
|
||||
if !w.sendEvent(path, Write) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -327,7 +367,7 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
||||
if events&unix.FILE_ATTRIB != 0 && stat != nil {
|
||||
// Only send Chmod if perms changed
|
||||
if stat.Mode().Perm() != fmode.Perm() {
|
||||
if !w.sendEvent(Event{Name: path, Op: Chmod}) {
|
||||
if !w.sendEvent(path, Chmod) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -336,27 +376,17 @@ func (w *fen) handleEvent(event *unix.PortEvent) error {
|
||||
if stat != nil {
|
||||
// If we get here, it means we've hit an event above that requires us to
|
||||
// continue watching the file or directory
|
||||
err := w.associateFile(path, stat, isWatched)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// Path may have been removed since the stat.
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
return w.associateFile(path, stat, isWatched)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The directory was modified, so we must find unwatched entities and watch
|
||||
// them. If something was removed from the directory, nothing will happen, as
|
||||
// everything else should still be watched.
|
||||
func (w *fen) updateDirectory(path string) error {
|
||||
// The directory was modified, so we must find unwatched entities and watch
|
||||
// them. If something was removed from the directory, nothing will happen,
|
||||
// as everything else should still be watched.
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
// Directory no longer exists: probably just deleted since we got the
|
||||
// event.
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -371,15 +401,10 @@ func (w *fen) updateDirectory(path string) error {
|
||||
return err
|
||||
}
|
||||
err = w.associateFile(path, finfo, false)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// File may have disappeared between getting the dir listing and
|
||||
// adding the port: that's okay to ignore.
|
||||
continue
|
||||
}
|
||||
if !w.sendError(err) {
|
||||
return nil
|
||||
}
|
||||
if !w.sendEvent(Event{Name: path, Op: Create}) {
|
||||
if !w.sendEvent(path, Create) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -405,7 +430,7 @@ func (w *fen) associateFile(path string, stat os.FileInfo, follow bool) error {
|
||||
// has fired but we haven't processed it yet.
|
||||
err := w.port.DissociatePath(path)
|
||||
if err != nil && !errors.Is(err, unix.ENOENT) {
|
||||
return fmt.Errorf("port.DissociatePath(%q): %w", path, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,22 +446,14 @@ func (w *fen) associateFile(path string, stat os.FileInfo, follow bool) error {
|
||||
if true {
|
||||
events |= unix.FILE_ATTRIB
|
||||
}
|
||||
err := w.port.AssociatePath(path, stat, events, stat.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("port.AssociatePath(%q): %w", path, err)
|
||||
}
|
||||
return nil
|
||||
return w.port.AssociatePath(path, stat, events, stat.Mode())
|
||||
}
|
||||
|
||||
func (w *fen) dissociateFile(path string, stat os.FileInfo, unused bool) error {
|
||||
if !w.port.PathIsWatched(path) {
|
||||
return nil
|
||||
}
|
||||
err := w.port.DissociatePath(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port.DissociatePath(%q): %w", path, err)
|
||||
}
|
||||
return nil
|
||||
return w.port.DissociatePath(path)
|
||||
}
|
||||
|
||||
func (w *fen) WatchList() []string {
|
||||
|
||||
435
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
435
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
@@ -19,7 +19,6 @@ import (
|
||||
)
|
||||
|
||||
type inotify struct {
|
||||
*shared
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
|
||||
@@ -28,6 +27,8 @@ type inotify struct {
|
||||
fd int
|
||||
inotifyFile *os.File
|
||||
watches *watches
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
doneMu sync.Mutex
|
||||
doneResp chan struct{} // Channel to respond to Close
|
||||
|
||||
// Store rename cookies in an array, with the index wrapping to 0. Almost
|
||||
@@ -51,6 +52,7 @@ type inotify struct {
|
||||
|
||||
type (
|
||||
watches struct {
|
||||
mu sync.RWMutex
|
||||
wd map[uint32]*watch // wd → watch
|
||||
path map[string]uint32 // pathname → wd
|
||||
}
|
||||
@@ -73,13 +75,34 @@ func newWatches() *watches {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watches) byPath(path string) *watch { return w.wd[w.path[path]] }
|
||||
func (w *watches) byWd(wd uint32) *watch { return w.wd[wd] }
|
||||
func (w *watches) len() int { return len(w.wd) }
|
||||
func (w *watches) add(ww *watch) { w.wd[ww.wd] = ww; w.path[ww.path] = ww.wd }
|
||||
func (w *watches) remove(watch *watch) { delete(w.path, watch.path); delete(w.wd, watch.wd) }
|
||||
func (w *watches) len() int {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return len(w.wd)
|
||||
}
|
||||
|
||||
func (w *watches) add(ww *watch) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.wd[ww.wd] = ww
|
||||
w.path[ww.path] = ww.wd
|
||||
}
|
||||
|
||||
func (w *watches) remove(wd uint32) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watch := w.wd[wd] // Could have had Remove() called. See #616.
|
||||
if watch == nil {
|
||||
return
|
||||
}
|
||||
delete(w.path, watch.path)
|
||||
delete(w.wd, wd)
|
||||
}
|
||||
|
||||
func (w *watches) removePath(path string) ([]uint32, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
path, recurse := recursivePath(path)
|
||||
wd, ok := w.path[path]
|
||||
if !ok {
|
||||
@@ -100,7 +123,7 @@ func (w *watches) removePath(path string) ([]uint32, error) {
|
||||
wds := make([]uint32, 0, 8)
|
||||
wds = append(wds, wd)
|
||||
for p, rwd := range w.path {
|
||||
if strings.HasPrefix(p, path) {
|
||||
if filepath.HasPrefix(p, path) {
|
||||
delete(w.path, p)
|
||||
delete(w.wd, rwd)
|
||||
wds = append(wds, rwd)
|
||||
@@ -109,7 +132,22 @@ func (w *watches) removePath(path string) ([]uint32, error) {
|
||||
return wds, nil
|
||||
}
|
||||
|
||||
func (w *watches) byPath(path string) *watch {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.wd[w.path[path]]
|
||||
}
|
||||
|
||||
func (w *watches) byWd(wd uint32) *watch {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.wd[wd]
|
||||
}
|
||||
|
||||
func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
var existing *watch
|
||||
wd, ok := w.path[path]
|
||||
if ok {
|
||||
@@ -132,9 +170,11 @@ func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error
|
||||
return nil
|
||||
}
|
||||
|
||||
var defaultBufferSize = 0
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return newBufferedBackend(0, ev, errs)
|
||||
}
|
||||
|
||||
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
|
||||
// Need to set nonblocking mode for SetDeadline to work, otherwise blocking
|
||||
// I/O operations won't terminate on close.
|
||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
||||
@@ -143,12 +183,12 @@ func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
}
|
||||
|
||||
w := &inotify{
|
||||
shared: newShared(ev, errs),
|
||||
Events: ev,
|
||||
Errors: errs,
|
||||
fd: fd,
|
||||
inotifyFile: os.NewFile(uintptr(fd), ""),
|
||||
watches: newWatches(),
|
||||
done: make(chan struct{}),
|
||||
doneResp: make(chan struct{}),
|
||||
}
|
||||
|
||||
@@ -156,10 +196,46 @@ func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Returns true if the event was sent, or false if watcher is closed.
|
||||
func (w *inotify) sendEvent(e Event) bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Events <- e:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the error was sent, or false if watcher is closed.
|
||||
func (w *inotify) sendError(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *inotify) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *inotify) Close() error {
|
||||
if w.shared.close() {
|
||||
w.doneMu.Lock()
|
||||
if w.isClosed() {
|
||||
w.doneMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
close(w.done)
|
||||
w.doneMu.Unlock()
|
||||
|
||||
// Causes any blocking reads to return with an error, provided the file
|
||||
// still supports deadline operations.
|
||||
@@ -168,7 +244,9 @@ func (w *inotify) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
<-w.doneResp // Wait for readEvents() to finish.
|
||||
// Wait for goroutine to close
|
||||
<-w.doneResp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -188,43 +266,6 @@ func (w *inotify) AddWith(path string, opts ...addOpt) error {
|
||||
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||
}
|
||||
|
||||
add := func(path string, with withOpts, recurse bool) error {
|
||||
var flags uint32
|
||||
if with.noFollow {
|
||||
flags |= unix.IN_DONT_FOLLOW
|
||||
}
|
||||
if with.op.Has(Create) {
|
||||
flags |= unix.IN_CREATE
|
||||
}
|
||||
if with.op.Has(Write) {
|
||||
flags |= unix.IN_MODIFY
|
||||
}
|
||||
if with.op.Has(Remove) {
|
||||
flags |= unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||
}
|
||||
if with.op.Has(Rename) {
|
||||
flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF
|
||||
}
|
||||
if with.op.Has(Chmod) {
|
||||
flags |= unix.IN_ATTRIB
|
||||
}
|
||||
if with.op.Has(xUnportableOpen) {
|
||||
flags |= unix.IN_OPEN
|
||||
}
|
||||
if with.op.Has(xUnportableRead) {
|
||||
flags |= unix.IN_ACCESS
|
||||
}
|
||||
if with.op.Has(xUnportableCloseWrite) {
|
||||
flags |= unix.IN_CLOSE_WRITE
|
||||
}
|
||||
if with.op.Has(xUnportableCloseRead) {
|
||||
flags |= unix.IN_CLOSE_NOWRITE
|
||||
}
|
||||
return w.register(path, flags, recurse)
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
path, recurse := recursivePath(path)
|
||||
if recurse {
|
||||
return filepath.WalkDir(path, func(root string, d fs.DirEntry, err error) error {
|
||||
@@ -248,11 +289,46 @@ func (w *inotify) AddWith(path string, opts ...addOpt) error {
|
||||
w.sendEvent(Event{Name: root, Op: Create})
|
||||
}
|
||||
|
||||
return add(root, with, true)
|
||||
return w.add(root, with, true)
|
||||
})
|
||||
}
|
||||
|
||||
return add(path, with, false)
|
||||
return w.add(path, with, false)
|
||||
}
|
||||
|
||||
func (w *inotify) add(path string, with withOpts, recurse bool) error {
|
||||
var flags uint32
|
||||
if with.noFollow {
|
||||
flags |= unix.IN_DONT_FOLLOW
|
||||
}
|
||||
if with.op.Has(Create) {
|
||||
flags |= unix.IN_CREATE
|
||||
}
|
||||
if with.op.Has(Write) {
|
||||
flags |= unix.IN_MODIFY
|
||||
}
|
||||
if with.op.Has(Remove) {
|
||||
flags |= unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||
}
|
||||
if with.op.Has(Rename) {
|
||||
flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF
|
||||
}
|
||||
if with.op.Has(Chmod) {
|
||||
flags |= unix.IN_ATTRIB
|
||||
}
|
||||
if with.op.Has(xUnportableOpen) {
|
||||
flags |= unix.IN_OPEN
|
||||
}
|
||||
if with.op.Has(xUnportableRead) {
|
||||
flags |= unix.IN_ACCESS
|
||||
}
|
||||
if with.op.Has(xUnportableCloseWrite) {
|
||||
flags |= unix.IN_CLOSE_WRITE
|
||||
}
|
||||
if with.op.Has(xUnportableCloseRead) {
|
||||
flags |= unix.IN_CLOSE_NOWRITE
|
||||
}
|
||||
return w.register(path, flags, recurse)
|
||||
}
|
||||
|
||||
func (w *inotify) register(path string, flags uint32, recurse bool) error {
|
||||
@@ -266,10 +342,6 @@ func (w *inotify) register(path string, flags uint32, recurse bool) error {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if e, ok := w.watches.wd[uint32(wd)]; ok {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
if existing == nil {
|
||||
return &watch{
|
||||
wd: uint32(wd),
|
||||
@@ -293,9 +365,6 @@ func (w *inotify) Remove(name string) error {
|
||||
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||||
time.Now().Format("15:04:05.000000000"), name)
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.remove(filepath.Clean(name))
|
||||
}
|
||||
|
||||
@@ -330,12 +399,13 @@ func (w *inotify) WatchList() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
entries := make([]string, 0, w.watches.len())
|
||||
w.watches.mu.RLock()
|
||||
for pathname := range w.watches.path {
|
||||
entries = append(entries, pathname)
|
||||
}
|
||||
w.watches.mu.RUnlock()
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
@@ -348,17 +418,21 @@ func (w *inotify) readEvents() {
|
||||
close(w.Events)
|
||||
}()
|
||||
|
||||
var buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
var (
|
||||
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
errno error // Syscall errno
|
||||
)
|
||||
for {
|
||||
// See if we have been closed.
|
||||
if w.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
n, err := w.inotifyFile.Read(buf[:])
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrClosed) {
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case errors.Unwrap(err) == os.ErrClosed:
|
||||
return
|
||||
case err != nil:
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
@@ -366,9 +440,13 @@ func (w *inotify) readEvents() {
|
||||
}
|
||||
|
||||
if n < unix.SizeofInotifyEvent {
|
||||
err := errors.New("notify: short read in readEvents()") // Read was too short.
|
||||
var err error
|
||||
if n == 0 {
|
||||
err = io.EOF // If EOF is received. This should really never happen.
|
||||
} else if n < 0 {
|
||||
err = errno // If an error occurred while reading.
|
||||
} else {
|
||||
err = errors.New("notify: short read in readEvents()") // Read was too short.
|
||||
}
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
@@ -376,135 +454,132 @@ func (w *inotify) readEvents() {
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't know how many events we just read into the buffer While the
|
||||
// offset points to at least one whole event.
|
||||
// We don't know how many events we just read into the buffer
|
||||
// While the offset points to at least one whole event...
|
||||
var offset uint32
|
||||
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||
// Point to the event in the buffer.
|
||||
inEvent := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
var (
|
||||
// Point "raw" to the event in the buffer
|
||||
raw = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
mask = uint32(raw.Mask)
|
||||
nameLen = uint32(raw.Len)
|
||||
// Move to the next event in the buffer
|
||||
next = func() { offset += unix.SizeofInotifyEvent + nameLen }
|
||||
)
|
||||
|
||||
if inEvent.Mask&unix.IN_Q_OVERFLOW != 0 {
|
||||
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||
if !w.sendError(ErrEventOverflow) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ev, ok := w.handleEvent(inEvent, &buf, offset)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !w.sendEvent(ev) {
|
||||
return
|
||||
/// If the event happened to the watched directory or the watched
|
||||
/// file, the kernel doesn't append the filename to the event, but
|
||||
/// we would like to always fill the the "Name" field with a valid
|
||||
/// filename. We retrieve the path of the watch from the "paths"
|
||||
/// map.
|
||||
watch := w.watches.byWd(uint32(raw.Wd))
|
||||
/// Can be nil if Remove() was called in another goroutine for this
|
||||
/// path inbetween reading the events from the kernel and reading
|
||||
/// the internal state. Not much we can do about it, so just skip.
|
||||
/// See #616.
|
||||
if watch == nil {
|
||||
next()
|
||||
continue
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
offset += unix.SizeofInotifyEvent + inEvent.Len
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offset uint32) (Event, bool) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
/// If the event happened to the watched directory or the watched file, the
|
||||
/// kernel doesn't append the filename to the event, but we would like to
|
||||
/// always fill the the "Name" field with a valid filename. We retrieve the
|
||||
/// path of the watch from the "paths" map.
|
||||
///
|
||||
/// Can be nil if Remove() was called in another goroutine for this path
|
||||
/// inbetween reading the events from the kernel and reading the internal
|
||||
/// state. Not much we can do about it, so just skip. See #616.
|
||||
watch := w.watches.byWd(uint32(inEvent.Wd))
|
||||
if watch == nil {
|
||||
return Event{}, true
|
||||
}
|
||||
|
||||
var (
|
||||
name = watch.path
|
||||
nameLen = uint32(inEvent.Len)
|
||||
)
|
||||
if nameLen > 0 {
|
||||
/// Point "bytes" at the first byte of the filename
|
||||
bb := *buf
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&bb[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||
/// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\x00")
|
||||
}
|
||||
|
||||
if debug {
|
||||
internal.Debug(name, inEvent.Mask, inEvent.Cookie)
|
||||
}
|
||||
|
||||
if inEvent.Mask&unix.IN_IGNORED != 0 || inEvent.Mask&unix.IN_UNMOUNT != 0 {
|
||||
w.watches.remove(watch)
|
||||
return Event{}, true
|
||||
}
|
||||
|
||||
// inotify will automatically remove the watch on deletes; just need
|
||||
// to clean our state here.
|
||||
if inEvent.Mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||
w.watches.remove(watch)
|
||||
}
|
||||
|
||||
// We can't really update the state when a watched path is moved; only
|
||||
// IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove the watch.
|
||||
if inEvent.Mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
|
||||
if watch.recurse { // Do nothing
|
||||
return Event{}, true
|
||||
}
|
||||
|
||||
err := w.remove(watch.path)
|
||||
if err != nil && !errors.Is(err, ErrNonExistentWatch) {
|
||||
if !w.sendError(err) {
|
||||
return Event{}, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip if we're watching both this path and the parent; the parent will
|
||||
/// already send a delete so no need to do it twice.
|
||||
if inEvent.Mask&unix.IN_DELETE_SELF != 0 {
|
||||
_, ok := w.watches.path[filepath.Dir(watch.path)]
|
||||
if ok {
|
||||
return Event{}, true
|
||||
}
|
||||
}
|
||||
|
||||
ev := w.newEvent(name, inEvent.Mask, inEvent.Cookie)
|
||||
// Need to update watch path for recurse.
|
||||
if watch.recurse {
|
||||
isDir := inEvent.Mask&unix.IN_ISDIR == unix.IN_ISDIR
|
||||
/// New directory created: set up watch on it.
|
||||
if isDir && ev.Has(Create) {
|
||||
err := w.register(ev.Name, watch.flags, true)
|
||||
if !w.sendError(err) {
|
||||
return Event{}, false
|
||||
name := watch.path
|
||||
if nameLen > 0 {
|
||||
/// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||
/// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||
}
|
||||
|
||||
// This was a directory rename, so we need to update all the
|
||||
// children.
|
||||
//
|
||||
// TODO: this is of course pretty slow; we should use a better data
|
||||
// structure for storing all of this, e.g. store children in the
|
||||
// watch. I have some code for this in my kqueue refactor we can use
|
||||
// in the future. For now I'm okay with this as it's not publicly
|
||||
// available. Correctness first, performance second.
|
||||
if ev.renamedFrom != "" {
|
||||
for k, ww := range w.watches.wd {
|
||||
if k == watch.wd || ww.path == ev.Name {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(ww.path, ev.renamedFrom) {
|
||||
ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1)
|
||||
w.watches.wd[k] = ww
|
||||
if debug {
|
||||
internal.Debug(name, raw.Mask, raw.Cookie)
|
||||
}
|
||||
|
||||
if mask&unix.IN_IGNORED != 0 { //&& event.Op != 0
|
||||
next()
|
||||
continue
|
||||
}
|
||||
|
||||
// inotify will automatically remove the watch on deletes; just need
|
||||
// to clean our state here.
|
||||
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||
w.watches.remove(watch.wd)
|
||||
}
|
||||
|
||||
// We can't really update the state when a watched path is moved;
|
||||
// only IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove
|
||||
// the watch.
|
||||
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
|
||||
if watch.recurse {
|
||||
next() // Do nothing
|
||||
continue
|
||||
}
|
||||
|
||||
err := w.remove(watch.path)
|
||||
if err != nil && !errors.Is(err, ErrNonExistentWatch) {
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip if we're watching both this path and the parent; the parent
|
||||
/// will already send a delete so no need to do it twice.
|
||||
if mask&unix.IN_DELETE_SELF != 0 {
|
||||
if _, ok := w.watches.path[filepath.Dir(watch.path)]; ok {
|
||||
next()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ev := w.newEvent(name, mask, raw.Cookie)
|
||||
// Need to update watch path for recurse.
|
||||
if watch.recurse {
|
||||
isDir := mask&unix.IN_ISDIR == unix.IN_ISDIR
|
||||
/// New directory created: set up watch on it.
|
||||
if isDir && ev.Has(Create) {
|
||||
err := w.register(ev.Name, watch.flags, true)
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
|
||||
// This was a directory rename, so we need to update all
|
||||
// the children.
|
||||
//
|
||||
// TODO: this is of course pretty slow; we should use a
|
||||
// better data structure for storing all of this, e.g. store
|
||||
// children in the watch. I have some code for this in my
|
||||
// kqueue refactor we can use in the future. For now I'm
|
||||
// okay with this as it's not publicly available.
|
||||
// Correctness first, performance second.
|
||||
if ev.renamedFrom != "" {
|
||||
w.watches.mu.Lock()
|
||||
for k, ww := range w.watches.wd {
|
||||
if k == watch.wd || ww.path == ev.Name {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(ww.path, ev.renamedFrom) {
|
||||
ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1)
|
||||
w.watches.wd[k] = ww
|
||||
}
|
||||
}
|
||||
w.watches.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the events that are not ignored on the events channel
|
||||
if !w.sendEvent(ev) {
|
||||
return
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
return ev, true
|
||||
}
|
||||
|
||||
func (w *inotify) isRecursive(path string) bool {
|
||||
@@ -575,8 +650,8 @@ func (w *inotify) xSupports(op Op) bool {
|
||||
}
|
||||
|
||||
func (w *inotify) state() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.watches.mu.Lock()
|
||||
defer w.watches.mu.Unlock()
|
||||
for wd, ww := range w.watches.wd {
|
||||
fmt.Fprintf(os.Stderr, "%4d: recurse=%t %q\n", wd, ww.recurse, ww.path)
|
||||
}
|
||||
|
||||
112
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
112
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
@@ -16,13 +16,14 @@ import (
|
||||
)
|
||||
|
||||
type kqueue struct {
|
||||
*shared
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
|
||||
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||
closepipe [2]int // Pipe used for closing kq.
|
||||
watches *watches
|
||||
done chan struct{}
|
||||
doneMu sync.Mutex
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -131,18 +132,14 @@ func (w *watches) byPath(path string) (watch, bool) {
|
||||
return info, ok
|
||||
}
|
||||
|
||||
func (w *watches) updateDirFlags(path string, flags uint32) bool {
|
||||
func (w *watches) updateDirFlags(path string, flags uint32) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
fd, ok := w.path[path]
|
||||
if !ok { // Already deleted: don't re-set it here.
|
||||
return false
|
||||
}
|
||||
fd := w.path[path]
|
||||
info := w.wd[fd]
|
||||
info.dirFlags = flags
|
||||
w.wd[fd] = info
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *watches) remove(fd int, path string) bool {
|
||||
@@ -182,20 +179,22 @@ func (w *watches) seenBefore(path string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
var defaultBufferSize = 0
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return newBufferedBackend(0, ev, errs)
|
||||
}
|
||||
|
||||
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
|
||||
kq, closepipe, err := newKqueue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := &kqueue{
|
||||
shared: newShared(ev, errs),
|
||||
Events: ev,
|
||||
Errors: errs,
|
||||
kq: kq,
|
||||
closepipe: closepipe,
|
||||
done: make(chan struct{}),
|
||||
watches: newWatches(),
|
||||
}
|
||||
|
||||
@@ -211,7 +210,7 @@ func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
// all.
|
||||
func newKqueue() (kq int, closepipe [2]int, err error) {
|
||||
kq, err = unix.Kqueue()
|
||||
if err != nil {
|
||||
if kq == -1 {
|
||||
return kq, closepipe, err
|
||||
}
|
||||
|
||||
@@ -240,17 +239,54 @@ func newKqueue() (kq int, closepipe [2]int, err error) {
|
||||
return kq, closepipe, nil
|
||||
}
|
||||
|
||||
// Returns true if the event was sent, or false if watcher is closed.
|
||||
func (w *kqueue) sendEvent(e Event) bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Events <- e:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the error was sent, or false if watcher is closed.
|
||||
func (w *kqueue) sendError(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *kqueue) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *kqueue) Close() error {
|
||||
if w.shared.close() {
|
||||
w.doneMu.Lock()
|
||||
if w.isClosed() {
|
||||
w.doneMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
close(w.done)
|
||||
w.doneMu.Unlock()
|
||||
|
||||
pathsToRemove := w.watches.listPaths(false)
|
||||
for _, name := range pathsToRemove {
|
||||
w.Remove(name)
|
||||
}
|
||||
|
||||
unix.Close(w.closepipe[1]) // Send "quit" message to readEvents
|
||||
// Send "quit" message to the reader goroutine.
|
||||
unix.Close(w.closepipe[1])
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -267,7 +303,7 @@ func (w *kqueue) AddWith(name string, opts ...addOpt) error {
|
||||
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||
}
|
||||
|
||||
_, err := w.addWatch(name, noteAllEvents, false)
|
||||
_, err := w.addWatch(name, noteAllEvents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -330,7 +366,7 @@ const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | un
|
||||
// described in kevent(2).
|
||||
//
|
||||
// Returns the real path to the file which was added, with symlinks resolved.
|
||||
func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, error) {
|
||||
func (w *kqueue) addWatch(name string, flags uint32) (string, error) {
|
||||
if w.isClosed() {
|
||||
return "", ErrClosed
|
||||
}
|
||||
@@ -349,15 +385,15 @@ func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, erro
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Follow symlinks, but only for paths added with Add(), and not paths
|
||||
// we're adding from internalWatch from a listdir.
|
||||
if !listDir && fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
// Follow symlinks.
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
link, err := os.Readlink(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !filepath.IsAbs(link) {
|
||||
link = filepath.Join(filepath.Dir(name), link)
|
||||
// Return nil because Linux can add unresolvable symlinks to the
|
||||
// watch list without problems, so maintain consistency with
|
||||
// that. There will be no file events for broken symlinks.
|
||||
// TODO: more specific check; returns os.PathError; ENOENT?
|
||||
return "", nil
|
||||
}
|
||||
|
||||
_, alreadyWatching = w.watches.byPath(link)
|
||||
@@ -372,7 +408,7 @@ func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, erro
|
||||
name = link
|
||||
fi, err = os.Lstat(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +422,7 @@ func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, erro
|
||||
if errors.Is(err, unix.EINTR) {
|
||||
continue
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -407,16 +444,10 @@ func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, erro
|
||||
if info.isDir {
|
||||
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||
(!alreadyWatching || (info.dirFlags&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||
if !w.watches.updateDirFlags(name, flags) {
|
||||
return "", nil
|
||||
}
|
||||
w.watches.updateDirFlags(name, flags)
|
||||
|
||||
if watchDir {
|
||||
d := name
|
||||
if info.linkName != "" {
|
||||
d = info.linkName
|
||||
}
|
||||
if err := w.watchDirectoryFiles(d); err != nil {
|
||||
if err := w.watchDirectoryFiles(name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -613,22 +644,19 @@ func (w *kqueue) dirChange(dir string) error {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("fsnotify.dirChange %q: %w", dir, err)
|
||||
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
fi, err := f.Info()
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||||
}
|
||||
|
||||
err = w.sendCreateIfNew(filepath.Join(dir, fi.Name()), fi)
|
||||
if err != nil {
|
||||
// Don't need to send an error if this file isn't readable.
|
||||
if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) || errors.Is(err, os.ErrNotExist) {
|
||||
if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||||
@@ -660,11 +688,11 @@ func (w *kqueue) internalWatch(name string, fi os.FileInfo) (string, error) {
|
||||
// mimic Linux providing delete events for subdirectories, but preserve
|
||||
// the flags used if currently watching subdirectory
|
||||
info, _ := w.watches.byPath(name)
|
||||
return w.addWatch(name, info.dirFlags|unix.NOTE_DELETE|unix.NOTE_RENAME, true)
|
||||
return w.addWatch(name, info.dirFlags|unix.NOTE_DELETE|unix.NOTE_RENAME)
|
||||
}
|
||||
|
||||
// Watch file to mimic Linux inotify.
|
||||
return w.addWatch(name, noteAllEvents, true)
|
||||
// watch file to mimic Linux inotify
|
||||
return w.addWatch(name, noteAllEvents)
|
||||
}
|
||||
|
||||
// Register events with the queue.
|
||||
@@ -694,9 +722,9 @@ func (w *kqueue) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
|
||||
}
|
||||
|
||||
func (w *kqueue) xSupports(op Op) bool {
|
||||
//if runtime.GOOS == "freebsd" {
|
||||
// return true // Supports everything.
|
||||
//}
|
||||
if runtime.GOOS == "freebsd" {
|
||||
//return true // Supports everything.
|
||||
}
|
||||
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
|
||||
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
|
||||
return false
|
||||
|
||||
5
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
5
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
@@ -9,11 +9,12 @@ type other struct {
|
||||
Errors chan error
|
||||
}
|
||||
|
||||
var defaultBufferSize = 0
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return nil, errors.New("fsnotify not supported on the current platform")
|
||||
}
|
||||
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
|
||||
return newBackend(ev, errs)
|
||||
}
|
||||
func (w *other) Close() error { return nil }
|
||||
func (w *other) WatchList() []string { return nil }
|
||||
func (w *other) Add(name string) error { return nil }
|
||||
|
||||
24
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
24
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
@@ -28,16 +28,18 @@ type readDirChangesW struct {
|
||||
|
||||
port windows.Handle // Handle to completion port
|
||||
input chan *input // Inputs to the reader are sent on this channel
|
||||
done chan chan<- error
|
||||
quit chan chan<- error
|
||||
|
||||
mu sync.Mutex // Protects access to watches, closed
|
||||
watches watchMap // Map of watches (key: i-number)
|
||||
closed bool // Set to true when Close() is first called
|
||||
}
|
||||
|
||||
var defaultBufferSize = 50
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
return newBufferedBackend(50, ev, errs)
|
||||
}
|
||||
|
||||
func newBufferedBackend(sz uint, ev chan Event, errs chan error) (backend, error) {
|
||||
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
|
||||
@@ -48,7 +50,7 @@ func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
port: port,
|
||||
watches: make(watchMap),
|
||||
input: make(chan *input, 1),
|
||||
done: make(chan chan<- error, 1),
|
||||
quit: make(chan chan<- error, 1),
|
||||
}
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
@@ -68,8 +70,8 @@ func (w *readDirChangesW) sendEvent(name, renamedFrom string, mask uint64) bool
|
||||
event := w.newEvent(name, uint32(mask))
|
||||
event.renamedFrom = renamedFrom
|
||||
select {
|
||||
case ch := <-w.done:
|
||||
w.done <- ch
|
||||
case ch := <-w.quit:
|
||||
w.quit <- ch
|
||||
case w.Events <- event:
|
||||
}
|
||||
return true
|
||||
@@ -81,10 +83,10 @@ func (w *readDirChangesW) sendError(err error) bool {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-w.done:
|
||||
return false
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
case <-w.quit:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,9 +99,9 @@ func (w *readDirChangesW) Close() error {
|
||||
w.closed = true
|
||||
w.mu.Unlock()
|
||||
|
||||
// Send "done" message to the reader goroutine
|
||||
// Send "quit" message to the reader goroutine
|
||||
ch := make(chan error)
|
||||
w.done <- ch
|
||||
w.quit <- ch
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -493,7 +495,7 @@ func (w *readDirChangesW) readEvents() {
|
||||
watch := (*watch)(unsafe.Pointer(ov))
|
||||
if watch == nil {
|
||||
select {
|
||||
case ch := <-w.done:
|
||||
case ch := <-w.quit:
|
||||
w.mu.Lock()
|
||||
var indexes []indexMap
|
||||
for _, index := range w.watches {
|
||||
|
||||
10
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
10
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
@@ -244,13 +244,12 @@ var (
|
||||
|
||||
// ErrUnsupported is returned by AddWith() when WithOps() specified an
|
||||
// Unportable event that's not supported on this platform.
|
||||
//lint:ignore ST1012 not relevant
|
||||
xErrUnsupported = errors.New("fsnotify: not supported with this backend")
|
||||
)
|
||||
|
||||
// NewWatcher creates a new Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
ev, errs := make(chan Event, defaultBufferSize), make(chan error)
|
||||
ev, errs := make(chan Event), make(chan error)
|
||||
b, err := newBackend(ev, errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -267,8 +266,8 @@ func NewWatcher() (*Watcher, error) {
|
||||
// cases, and whenever possible you will be better off increasing the kernel
|
||||
// buffers instead of adding a large userspace buffer.
|
||||
func NewBufferedWatcher(sz uint) (*Watcher, error) {
|
||||
ev, errs := make(chan Event, sz), make(chan error)
|
||||
b, err := newBackend(ev, errs)
|
||||
ev, errs := make(chan Event), make(chan error)
|
||||
b, err := newBufferedBackend(sz, ev, errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -338,8 +337,7 @@ func (w *Watcher) Close() error { return w.b.Close() }
|
||||
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
||||
// yet removed).
|
||||
//
|
||||
// The order is undefined, and may differ per call. Returns nil if
|
||||
// [Watcher.Close] was called.
|
||||
// Returns nil if [Watcher.Close] was called.
|
||||
func (w *Watcher) WatchList() []string { return w.b.WatchList() }
|
||||
|
||||
// Supports reports if all the listed operations are supported by this platform.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user