minimal report handling

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2022-04-13 15:22:02 +00:00
parent a737f4e0a7
commit b4017a0451
18 changed files with 217 additions and 58 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ We also suggest to use the last port in your extensions' range as a debug/metric
| 9205-9209 | [markdown-editor](https://github.com/owncloud/ocis-markdown-editor) |
| 9210-9214 | [reva](https://github.com/owncloud/ocis-reva) unused? |
| 9215-9219 | reva metadata storage |
| 9220-9224 | FREE |
| 9220-9224 | search |
| 9225-9229 | photoprism (state: PoC) |
| 9230-9234 | [nats](https://github.com/owncloud/ocis/tree/master/nats) |
| 9235-9239 | idm TBD |
+5 -2
View File
@@ -34,10 +34,13 @@ For now, the storage service uses these ports to preconfigure those services:
| 9159 | storage users debug |
| 9160 | groups |
| 9161 | groups debug |
| 9164 | storage appprovider |
| 9165 | storage appprovider debug |
| 9164 | storage appprovider |
| 9165 | storage appprovider debug |
| 9178 | storage public link |
| 9179 | storage public link data |
| 9180 | accounts grpc |
| 9181 | accounts http |
| 9182 | accounts debug |
| 9215 | storage meta grpc |
| 9216 | storage meta http |
| 9217 | storage meta debug |
+2
View File
@@ -14,6 +14,7 @@ import (
notifications "github.com/owncloud/ocis/notifications/pkg/config"
ocs "github.com/owncloud/ocis/ocs/pkg/config"
proxy "github.com/owncloud/ocis/proxy/pkg/config"
search "github.com/owncloud/ocis/search/pkg/config"
settings "github.com/owncloud/ocis/settings/pkg/config"
storage "github.com/owncloud/ocis/storage/pkg/config"
store "github.com/owncloud/ocis/store/pkg/config"
@@ -71,6 +72,7 @@ type Config struct {
OCS *ocs.Config `yaml:"ocs"`
Web *web.Config `yaml:"web"`
Proxy *proxy.Config `yaml:"proxy"`
Search *search.Config `yaml:"search"`
Settings *settings.Config `yaml:"settings"`
Storage *storage.Config `yaml:"storage"`
Store *store.Config `yaml:"store"`
+2
View File
@@ -12,6 +12,7 @@ import (
notifications "github.com/owncloud/ocis/notifications/pkg/config/defaults"
ocs "github.com/owncloud/ocis/ocs/pkg/config/defaults"
proxy "github.com/owncloud/ocis/proxy/pkg/config/defaults"
search "github.com/owncloud/ocis/search/pkg/config/defaults"
settings "github.com/owncloud/ocis/settings/pkg/config/defaults"
storage "github.com/owncloud/ocis/storage/pkg/config/defaults"
store "github.com/owncloud/ocis/store/pkg/config/defaults"
@@ -40,6 +41,7 @@ func DefaultConfig() *Config {
Proxy: proxy.DefaultConfig(),
GraphExplorer: graphExplorer.DefaultConfig(),
OCS: ocs.DefaultConfig(),
Search: search.DefaultConfig(),
Settings: settings.DefaultConfig(),
Web: web.DefaultConfig(),
Store: store.DefaultConfig(),
+2
View File
@@ -32,6 +32,7 @@ import (
"github.com/owncloud/ocis/ocis-pkg/log"
ocs "github.com/owncloud/ocis/ocs/pkg/command"
proxy "github.com/owncloud/ocis/proxy/pkg/command"
search "github.com/owncloud/ocis/search/pkg/command"
settings "github.com/owncloud/ocis/settings/pkg/command"
storage "github.com/owncloud/ocis/storage/pkg/command"
store "github.com/owncloud/ocis/store/pkg/command"
@@ -120,6 +121,7 @@ func NewService(options ...Option) (*Service, error) {
s.ServicesRegistry["storage-public-link"] = storage.NewStoragePublicLink
s.ServicesRegistry["storage-appprovider"] = storage.NewAppProvider
s.ServicesRegistry["notifications"] = notifications.NewSutureService
s.ServicesRegistry["search"] = search.NewSutureService
// populate delayed services
s.Delayed["storage-sharing"] = storage.NewSharing
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/messages/search/v0/search.proto
package v0
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/messages/store/v0/store.proto
package v0
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// protoc-gen-go v1.28.0
// protoc (unknown)
// source: ocis/services/accounts/v0/accounts.proto
package v0
+1 -9
View File
@@ -12,7 +12,6 @@ import (
"github.com/owncloud/ocis/search/pkg/metrics"
"github.com/owncloud/ocis/search/pkg/server/debug"
"github.com/owncloud/ocis/search/pkg/server/grpc"
svc "github.com/owncloud/ocis/search/pkg/service/v0"
"github.com/owncloud/ocis/search/pkg/tracing"
"github.com/urfave/cli/v2"
)
@@ -40,24 +39,17 @@ func Server(cfg *config.Config) *cli.Command {
}
return context.WithCancel(cfg.Context)
}()
mtrcs := metrics.New()
defer cancel()
mtrcs := metrics.New()
mtrcs.BuildInfo.WithLabelValues(version.String).Set(1)
handler, err := svc.New(svc.Logger(logger), svc.Config(cfg))
if err != nil {
logger.Error().Err(err).Msg("handler init")
return err
}
grpcServer := grpc.Server(
grpc.Config(cfg),
grpc.Logger(logger),
grpc.Name(cfg.Service.Name),
grpc.Context(ctx),
grpc.Metrics(mtrcs),
grpc.Handler(handler),
)
gr.Add(grpcServer.Run, func(_ error) {
+1 -1
View File
@@ -24,7 +24,7 @@ func Version(cfg *config.Config) *cli.Command {
fmt.Println("")
reg := registry.GetRegistry()
services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name)
services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name)
if err != nil {
fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err))
return err
-1
View File
@@ -16,7 +16,6 @@ type Config struct {
Log *Log `ocisConfig:"log"`
Debug Debug `ocisConfig:"debug"`
HTTP HTTP `ocisConfig:"http"`
GRPC GRPC `ocisConfig:"grpc"`
Reva Reva `ocisConfig:"reva"`
+3 -13
View File
@@ -1,24 +1,17 @@
package defaults
import (
"strings"
"github.com/owncloud/ocis/search/pkg/config"
)
func DefaultConfig() *config.Config {
return &config.Config{
Debug: config.Debug{
Addr: "127.0.0.1:9124",
Addr: "127.0.0.1:9224",
Token: "",
},
HTTP: config.HTTP{
Addr: "127.0.0.1:9120",
Namespace: "com.owncloud.search",
Root: "/search",
},
GRPC: config.GRPC{
Addr: "127.0.0.1:9180",
Addr: "127.0.0.1:9220",
Namespace: "com.owncloud.api",
},
Service: config.Service{
@@ -59,8 +52,5 @@ func EnsureDefaults(cfg *config.Config) {
}
func Sanitize(cfg *config.Config) {
// sanitize config
if cfg.HTTP.Root != "/" {
cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/")
}
// no http endpoint to be sanitized
}
+5 -5
View File
@@ -108,17 +108,17 @@ func BuildMapping() mapping.IndexMapping {
func toEntity(ref *sprovider.Reference, ri *sprovider.ResourceInfo) *indexDocument {
return &indexDocument{
RootID: ref.ResourceId.GetStorageId() + ":" + ref.ResourceId.GetOpaqueId(),
RootID: ref.ResourceId.GetStorageId() + "!" + ref.ResourceId.GetOpaqueId(),
Path: ref.Path,
ID: ri.Id.GetStorageId() + ":" + ri.Id.GetOpaqueId(),
ID: ri.Id.GetStorageId() + "!" + ri.Id.GetOpaqueId(),
Name: ri.Path,
Size: ri.Size,
}
}
func fromFields(fields map[string]interface{}) (*searchmsg.Match, error) {
rootIDParts := strings.SplitN(fields["RootID"].(string), ":", 2)
IDParts := strings.SplitN(fields["ID"].(string), ":", 2)
rootIDParts := strings.SplitN(fields["RootID"].(string), "!", 2)
IDParts := strings.SplitN(fields["ID"].(string), "!", 2)
return &searchmsg.Match{
Entity: &searchmsg.Entity{
@@ -140,5 +140,5 @@ func fromFields(fields map[string]interface{}) (*searchmsg.Match, error) {
}
func idToBleveId(id *sprovider.ResourceId) string {
return id.StorageId + ":" + id.OpaqueId
return id.StorageId + "!" + id.OpaqueId
}
-4
View File
@@ -23,10 +23,6 @@ func Server(opts ...Option) (*http.Server, error) {
debug.Zpages(options.Config.Debug.Zpages),
debug.Health(health(options.Config)),
debug.Ready(ready(options.Config)),
debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins),
debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods),
debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
), nil
}
+14 -4
View File
@@ -4,12 +4,12 @@ import (
"github.com/owncloud/ocis/ocis-pkg/service/grpc"
"github.com/owncloud/ocis/ocis-pkg/version"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
svc "github.com/owncloud/ocis/search/pkg/service/v0"
)
// Server initializes a new go-micro service ready to run
func Server(opts ...Option) grpc.Service {
options := newOptions(opts...)
handler := options.Handler
service := grpc.NewService(
grpc.Name(options.Config.Service.Name),
@@ -21,9 +21,19 @@ func Server(opts ...Option) grpc.Service {
grpc.Version(version.String),
)
if err := searchsvc.RegisterSearchProviderHandler(service.Server(), handler); err != nil {
options.Logger.Fatal().Err(err).Msg("could not register service handler")
handle, err := svc.NewHandler(
svc.Config(options.Config),
svc.Logger(options.Logger),
)
if err != nil {
options.Logger.Error().
Err(err).
Msg("Error initializing search service")
return grpc.Service{}
}
_ = searchsvc.RegisterSearchProviderHandler(
service.Server(),
handle,
)
return service
}
+10 -6
View File
@@ -1,21 +1,21 @@
package service
import (
"context"
"github.com/blevesearch/bleve/v2"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/owncloud/ocis/ocis-pkg/log"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
"github.com/owncloud/ocis/search/pkg/config"
"github.com/owncloud/ocis/search/pkg/search"
"github.com/owncloud/ocis/search/pkg/search/index"
searchprovider "github.com/owncloud/ocis/search/pkg/search/provider"
)
// userDefaultGID is the default integer representing the "users" group.
const userDefaultGID = 30000
// New returns a new instance of Service
func New(opts ...Option) (*Service, error) {
// NewHandler returns a service implementation for Service.
func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, error) {
options := newOptions(opts...)
logger := options.Logger
cfg := options.Config
@@ -31,7 +31,7 @@ func New(opts ...Option) (*Service, error) {
gwclient, err := pool.GetGatewayServiceClient(cfg.Reva.Address)
if err != nil {
logger.Fatal().Msgf("could not get reva client at address %s", cfg.Reva.Address)
logger.Fatal().Err(err).Str("addr", cfg.Reva.Address).Msg("could not get reva client")
}
provider := searchprovider.New(gwclient, index)
@@ -51,3 +51,7 @@ type Service struct {
Config *config.Config
provider search.ProviderClient
}
func (s Service) Search(ctx context.Context, in *searchsvc.SearchRequest, out *searchsvc.SearchResponse) error {
return nil
}
+161 -3
View File
@@ -1,16 +1,38 @@
package svc
import (
"context"
"encoding/xml"
"io"
"net/http"
"strconv"
searchmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/search/v0"
searchsvc "github.com/owncloud/ocis/protogen/gen/ocis/services/search/v0"
"github.com/owncloud/ocis/webdav/pkg/net"
"github.com/owncloud/ocis/webdav/pkg/prop"
"github.com/owncloud/ocis/webdav/pkg/propfind"
merrors "go-micro.dev/v4/errors"
)
const (
elementNameSearchFiles = "search-files"
// TODO elementNameFilterFiles = "filter-files"
)
// Search is the endpoint for retrieving search results for REPORT requests
func (g Webdav) Search(w http.ResponseWriter, r *http.Request) {
rsp, err := g.searchClient.Search(r.Context(), &searchsvc.SearchRequest{})
rep, err := readReport(r.Body)
if err != nil {
renderError(w, r, errBadRequest(err.Error()))
g.log.Error().Err(err).Msg("error reading report")
return
}
rsp, err := g.searchClient.Search(r.Context(), &searchsvc.SearchRequest{
Query: rep.SearchFiles.Search.Pattern,
})
if err != nil {
e := merrors.Parse(err.Error())
switch e.Code {
@@ -28,6 +50,142 @@ func (g Webdav) Search(w http.ResponseWriter, r *http.Request) {
func (g Webdav) sendSearchResponse(rsp *searchsvc.SearchResponse, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
responsesXML, err := multistatusResponse(r.Context(), rsp.Matches)
if err != nil {
g.log.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol")
w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8")
w.WriteHeader(http.StatusMultiStatus)
if _, err := w.Write(responsesXML); err != nil {
g.log.Err(err).Msg("error writing response")
}
}
// multistatusResponse converts a list of matches into a multistatus response string
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, matches[i])
if err != nil {
return nil, err
}
responses = append(responses, res)
}
msr := propfind.NewMultiStatusResponseXML()
msr.Responses = responses
msg, err := xml.Marshal(msr)
if err != nil {
return nil, err
}
return msg, nil
}
func matchToPropResponse(ctx context.Context, match *searchmsg.Match) (*propfind.ResponseXML, error) {
response := propfind.ResponseXML{
Href: net.EncodePath(match.Entity.Ref.Path),
Propstat: []propfind.PropstatXML{},
}
propstatOK := propfind.PropstatXML{
Status: "HTTP/1.1 200 OK",
Prop: []prop.PropertyXML{},
}
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:id", match.Entity.Id.StorageId+"!"+match.Entity.Id.OpaqueId))
size := strconv.FormatUint(match.Entity.Size, 10)
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:size", size))
// TODO find name for score property
score := strconv.FormatFloat(float64(match.Score), 'f', -1, 64)
propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:score", score))
if len(propstatOK.Prop) > 0 {
response.Propstat = append(response.Propstat, propstatOK)
}
return &response, nil
}
type report struct {
SearchFiles *reportSearchFiles
// FilterFiles TODO add this for tag based search
FilterFiles *reportFilterFiles `xml:"filter-files"`
}
type reportSearchFiles struct {
XMLName xml.Name `xml:"search-files"`
Lang string `xml:"xml:lang,attr,omitempty"`
Prop Props `xml:"DAV: prop"`
Search reportSearchFilesSearch `xml:"search"`
}
type reportSearchFilesSearch struct {
Pattern string `xml:"search"`
Limit int `xml:"limit"`
Offset int `xml:"offset"`
}
type reportFilterFiles struct {
XMLName xml.Name `xml:"filter-files"`
Lang string `xml:"xml:lang,attr,omitempty"`
Prop Props `xml:"DAV: prop"`
Rules reportFilterFilesRules `xml:"filter-rules"`
}
type reportFilterFilesRules struct {
Favorite bool `xml:"favorite"`
SystemTag int `xml:"systemtag"`
}
// Props represents properties related to a resource
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind)
type Props []xml.Name
// XML holds the xml representation of a propfind
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind
type XML struct {
XMLName xml.Name `xml:"DAV: propfind"`
Allprop *struct{} `xml:"DAV: allprop"`
Propname *struct{} `xml:"DAV: propname"`
Prop Props `xml:"DAV: prop"`
Include Props `xml:"DAV: include"`
}
func readReport(r io.Reader) (rep *report, err error) {
decoder := xml.NewDecoder(r)
rep = &report{}
for {
t, err := decoder.Token()
if err == io.EOF {
// io.EOF is a successful end
return rep, nil
}
if err != nil {
return nil, err
}
if v, ok := t.(xml.StartElement); ok {
if v.Name.Local == elementNameSearchFiles {
var repSF reportSearchFiles
err = decoder.DecodeElement(&repSF, &v)
if err != nil {
return nil, err
}
rep.SearchFiles = &repSF
/*
} else if v.Name.Local == elementNameFilterFiles {
var repFF reportFilterFiles
err = decoder.DecodeElement(&repFF, &v)
if err != nil {
return nil, http.StatusBadRequest, err
}
rep.FilterFiles = &repFF
*/
}
}
}
}
+4 -3
View File
@@ -52,6 +52,7 @@ func NewService(opts ...Option) (Service, error) {
conf := options.Config
m := chi.NewMux()
chi.RegisterMethod("REPORT")
m.Use(options.Middleware...)
gwc, err := pool.GetGatewayServiceClient(conf.RevaGateway)
@@ -63,7 +64,7 @@ func NewService(opts ...Option) (Service, error) {
config: conf,
log: options.Logger,
mux: m,
searchClient: searchsvc.NewSearchProviderService("search", grpc.DefaultClient),
searchClient: searchsvc.NewSearchProviderService("com.owncloud.api.search", grpc.DefaultClient),
thumbnailsClient: thumbnailssvc.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient),
revaClient: gwc,
}
@@ -74,13 +75,13 @@ func NewService(opts ...Option) (Service, error) {
r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail)
r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead)
r.MethodFunc("REPORT", "/remote.php/dav/files/{id}/*", svc.Search)
r.MethodFunc("REPORT", "/remote.php/dav/files/{id}", svc.Search)
})
return svc, nil
}
// Webdav defines implements the business logic for Service.
// Webdav implements the business logic for Service.
type Webdav struct {
config *config.Config
log log.Logger