From 40e15362cc995969f3f80a58793c13d644b62717 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Tue, 14 Sep 2021 19:03:19 +0200 Subject: [PATCH 1/9] Expose extra properties for mimetypes in the /app/list HTTP endpoint --- changelog/unreleased/appreg-mimetypes.md | 4 ++ go.mod | 1 + .../http/services/appprovider/appprovider.go | 48 +++++-------------- pkg/app/app.go | 2 +- pkg/app/registry/static/static.go | 20 +++++--- 5 files changed, 33 insertions(+), 42 deletions(-) create mode 100644 changelog/unreleased/appreg-mimetypes.md diff --git a/changelog/unreleased/appreg-mimetypes.md b/changelog/unreleased/appreg-mimetypes.md new file mode 100644 index 0000000000..0083f3c078 --- /dev/null +++ b/changelog/unreleased/appreg-mimetypes.md @@ -0,0 +1,4 @@ +Enhancement: handle mimetypes and their friendly names + +https://github.com/cs3org/cs3apis/pull/145 +https://github.com/cs3org/reva/pull/2067 diff --git a/go.mod b/go.mod index 78d6b87afa..07c51643cd 100644 --- a/go.mod +++ b/go.mod @@ -83,4 +83,5 @@ replace ( github.com/eventials/go-tus => github.com/andrewmostello/go-tus v0.0.0-20200314041820-904a9904af9a github.com/oleiade/reflections => github.com/oleiade/reflections v1.0.1 google.golang.org/grpc => google.golang.org/grpc v1.26.0 // temporary downgrade + github.com/cs3org/go-cs3apis => /home/lopresti/CS3API/build/go-cs3apis ) diff --git a/internal/http/services/appprovider/appprovider.go b/internal/http/services/appprovider/appprovider.go index 22c0a3e775..a632f50d15 100644 --- a/internal/http/services/appprovider/appprovider.go +++ b/internal/http/services/appprovider/appprovider.go @@ -124,16 +124,9 @@ func (s *svc) handleList(w http.ResponseWriter, r *http.Request) { return } - mimeTypes := listRes.MimeTypes - filterAppsByUserAgent(mimeTypes, r.UserAgent()) - - filterMimeTypes(mimeTypes) - - if mimeTypes == nil { - mimeTypes = make(map[string]*appregistry.AppProviderList) // ensure array empty object instead of null in json - } - - js, err := json.Marshal(map[string]interface{}{"mime-types": mimeTypes}) + res := listRes.MimeTypes + filterAppsByUserAgent(res, r.UserAgent()) + js, err := json.Marshal(map[string]interface{}{"mime-types": res}) if err != nil { ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error marshalling JSON response", err) return @@ -189,39 +182,24 @@ func (s *svc) handleOpen(w http.ResponseWriter, r *http.Request) { } } -func filterMimeTypes(mimeTypes map[string]*appregistry.AppProviderList) { - for m, providers := range mimeTypes { +func filterAppsByUserAgent(mimeTypes *appregistry.MimeTypeList, userAgent string) { + ua := ua.Parse(userAgent) + res := []*appregistry.MimeTypeInfo{} + for _, m := range mimeTypes.MimeTypes { apps := []*appregistry.ProviderInfo{} - for _, p := range providers.AppProviders { + for _, p := range m.AppProviders { p.Address = "" // address is internal only and not needed in the client - // apps are called by name, so if it has no name you cannot call it. Therefore we should not advertise it. - if p.Name != "" { + // apps are called by name, so if it has no name it cannot be called and should not be advertised + // also filter Desktop-only apps if ua is not Desktop + if p.Name != "" && (ua.Desktop || !p.DesktopOnly) { apps = append(apps, p) } } if len(apps) > 0 { - mimeTypes[m] = &appregistry.AppProviderList{AppProviders: apps} - } else { - delete(mimeTypes, m) - } - } -} - -func filterAppsByUserAgent(mimeTypes map[string]*appregistry.AppProviderList, userAgent string) { - ua := ua.Parse(userAgent) - if ua.Desktop { - return - } - - for m, providers := range mimeTypes { - apps := []*appregistry.ProviderInfo{} - for _, p := range providers.AppProviders { - if !p.DesktopOnly { - apps = append(apps, p) - } + res = append(res, m) } - mimeTypes[m] = &appregistry.AppProviderList{AppProviders: apps} } + mimeTypes.MimeTypes = res } func (s *svc) getStatInfo(ctx context.Context, fileID string, client gateway.GatewayAPIClient) (*provider.ResourceInfo, ocmd.APIErrorCode, error) { diff --git a/pkg/app/app.go b/pkg/app/app.go index bb1319dc52..f99dcef71f 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -31,7 +31,7 @@ import ( type Registry interface { FindProviders(ctx context.Context, mimeType string) ([]*registry.ProviderInfo, error) ListProviders(ctx context.Context) ([]*registry.ProviderInfo, error) - ListSupportedMimeTypes(ctx context.Context) (map[string]*registry.AppProviderList, error) + ListSupportedMimeTypes(ctx context.Context) (*registry.MimeTypeList, error) AddProvider(ctx context.Context, p *registry.ProviderInfo) error GetDefaultProviderForMimeType(ctx context.Context, mimeType string) (*registry.ProviderInfo, error) SetDefaultProviderForMimeType(ctx context.Context, mimeType string, p *registry.ProviderInfo) error diff --git a/pkg/app/registry/static/static.go b/pkg/app/registry/static/static.go index 6ddb63864a..d95577ed7f 100644 --- a/pkg/app/registry/static/static.go +++ b/pkg/app/registry/static/static.go @@ -148,23 +148,31 @@ func (b *reg) ListProviders(ctx context.Context) ([]*registrypb.ProviderInfo, er return providers, nil } -func (b *reg) ListSupportedMimeTypes(ctx context.Context) (map[string]*registrypb.AppProviderList, error) { +func (b *reg) ListSupportedMimeTypes(ctx context.Context) (*registrypb.MimeTypeList, error) { b.RLock() defer b.RUnlock() - mimeTypes := make(map[string]*registrypb.AppProviderList) + res := registrypb.MimeTypeList{MimeTypes: []*registrypb.MimeTypeInfo{}} + mtmap := make(map[string]*registrypb.MimeTypeInfo) for _, p := range b.providers { t := *p t.MimeTypes = nil for _, m := range p.MimeTypes { - if _, ok := mimeTypes[m]; ok { - mimeTypes[m].AppProviders = append(mimeTypes[m].AppProviders, &t) + if _, ok := mtmap[m]; ok { + mtmap[m].AppProviders = append(mtmap[m].AppProviders, &t) } else { - mimeTypes[m] = ®istrypb.AppProviderList{AppProviders: []*registrypb.ProviderInfo{&t}} + mtmap[m] = ®istrypb.MimeTypeInfo{ + AppProviders: []*registrypb.ProviderInfo{&t}, + Ext: "", // TODO fetch from config + Name: "", + Description: "", + Icon: "", + } + res.MimeTypes = append(res.MimeTypes, mtmap[m]) } } } - return mimeTypes, nil + return &res, nil } func (b *reg) SetDefaultProviderForMimeType(ctx context.Context, mimeType string, p *registrypb.ProviderInfo) error { From 714f58f2abca45632ca97db6f0b8103b0dcee0b5 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Fri, 17 Sep 2021 11:03:26 +0200 Subject: [PATCH 2/9] Boiler plate code to implement the /app/new HTTP endpoint This includes the GRPC gateway, the appprovider and its drivers --- changelog/unreleased/appreg-mimetypes.md | 6 ++- .../grpc/services/appprovider/appprovider.go | 18 ++++++++ internal/grpc/services/gateway/appprovider.go | 45 +++++++++++++++++++ .../http/services/appprovider/appprovider.go | 45 +++++++++++++++++++ pkg/app/app.go | 3 +- pkg/app/provider/demo/demo.go | 16 ++++++- pkg/app/provider/wopi/wopi.go | 14 +++++- 7 files changed, 142 insertions(+), 5 deletions(-) diff --git a/changelog/unreleased/appreg-mimetypes.md b/changelog/unreleased/appreg-mimetypes.md index 0083f3c078..862f962145 100644 --- a/changelog/unreleased/appreg-mimetypes.md +++ b/changelog/unreleased/appreg-mimetypes.md @@ -1,4 +1,8 @@ -Enhancement: handle mimetypes and their friendly names +Enhancement: UI improvements for the AppProviders + +Mime types and their friendly names are now handled in +the /app/list HTTP endpoint, and an additional /app/new +endpoint is made available to create new files for apps. https://github.com/cs3org/cs3apis/pull/145 https://github.com/cs3org/reva/pull/2067 diff --git a/internal/grpc/services/appprovider/appprovider.go b/internal/grpc/services/appprovider/appprovider.go index 8dd446d704..c12acfca78 100644 --- a/internal/grpc/services/appprovider/appprovider.go +++ b/internal/grpc/services/appprovider/appprovider.go @@ -142,6 +142,24 @@ func getProvider(c *config) (app.Provider, error) { return nil, errtypes.NotFound("driver not found: " + c.Driver) } +func (s *service) CreateFileForApp(ctx context.Context, req *providerpb.CreateFileForAppRequest) (*providerpb.CreateFileForAppResponse, error) { + fileInfo, err := s.provider.CreateFile(ctx, req.Ref, req.Filename, req.Template) + if err != nil { + err := errors.Wrap(err, "appprovider: error calling CreateFile") + res := &providerpb.CreateFileForAppResponse{ + Status: status.NewInternal(ctx, err, "error creating file"), + } + return res, nil + } + + res := &providerpb.CreateFileForAppResponse{ + Status: status.NewOK(ctx), + ResourceInfo: fileInfo, + } + return res, nil + +} + func (s *service) OpenInApp(ctx context.Context, req *providerpb.OpenInAppRequest) (*providerpb.OpenInAppResponse, error) { appURL, err := s.provider.GetAppURL(ctx, req.ResourceInfo, req.ViewMode, req.AccessToken) if err != nil { diff --git a/internal/grpc/services/gateway/appprovider.go b/internal/grpc/services/gateway/appprovider.go index b834968ced..bd577f72ad 100644 --- a/internal/grpc/services/gateway/appprovider.go +++ b/internal/grpc/services/gateway/appprovider.go @@ -42,6 +42,51 @@ import ( "google.golang.org/grpc/metadata" ) +func (s *svc) CreateFileForApp(ctx context.Context, req *providerpb.CreateFileForAppRequest) (*providerpb.CreateFileForAppResponse, error) { + _, st := s.getPath(ctx, req.Ref) + if st.Code != rpc.Code_CODE_OK { + if st.Code == rpc.Code_CODE_NOT_FOUND { + return &providerpb.CreateFileForAppResponse{ + Status: status.NewNotFound(ctx, "gateway: container resource not found:"+req.Ref.String()), + }, nil + } + return &providerpb.CreateFileForAppResponse{ + Status: st, + }, nil + } + + // TODO identify app provider: take the GetDefaultProviderForMimeType for the given file extension + // provider, err := s.getDefaultAppProvider(ctx, req.Ref.path) + // if err != nil { + // err = errors.Wrap(err, "gateway: error calling findAppProvider") + // var st *rpc.Status + // if _, ok := err.(errtypes.IsNotFound); ok { + // st = status.NewNotFound(ctx, "app provider not found") + // } else { + // st = status.NewInternal(ctx, err, "error searching for app provider") + // } + // return &providerpb.CreateFileForAppResponse{ + // Status: st, + // }, nil + // } + + // appProviderClient, err := pool.GetAppProviderClient(provider.Address) + // if err != nil { + // err = errors.Wrap(err, "gateway: error calling GetAppProviderClient") + // return &providerpb.CreateFileForAppResponse{ + // Status: status.NewInternal(ctx, err, "error getting appprovider client"), + // }, nil + // } + + // res, err := appProviderClient.CreateFileForApp(ctx, req) + // if err != nil { + // return nil, errors.Wrap(err, "gateway: error calling CreateFileForApp") + // } + res := &providerpb.CreateFileForAppResponse{} + + return res, nil +} + func (s *svc) OpenInApp(ctx context.Context, req *gateway.OpenInAppRequest) (*providerpb.OpenInAppResponse, error) { p, st := s.getPath(ctx, req.Ref) if st.Code != rpc.Code_CODE_OK { diff --git a/internal/http/services/appprovider/appprovider.go b/internal/http/services/appprovider/appprovider.go index a632f50d15..71d55217e5 100644 --- a/internal/http/services/appprovider/appprovider.go +++ b/internal/http/services/appprovider/appprovider.go @@ -26,6 +26,7 @@ import ( "strings" "unicode/utf8" + appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" @@ -98,6 +99,8 @@ func (s *svc) Handler() http.Handler { head, r.URL.Path = router.ShiftPath(r.URL.Path) switch head { + case "new": + s.handleNew(w, r) case "list": s.handleList(w, r) case "open": @@ -106,6 +109,48 @@ func (s *svc) Handler() http.Handler { }) } +func (s *svc) handleNew(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc) + if err != nil { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error getting grpc gateway client", err) + return + } + + info, errCode, err := s.getStatInfo(ctx, r.URL.Query().Get("container"), client) + if err != nil { + ocmd.WriteError(w, r, errCode, "error statting container", err) + return + } + + createReq := appprovider.CreateFileForAppRequest{ + Ref: &provider.Reference{ResourceId: info.Id}, + Filename: r.URL.Query().Get("filename"), + } + createRes, err := client.CreateFileForApp(ctx, &createReq) + if err != nil { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error creating resource", err) + return + } + if createRes.Status.Code != rpc.Code_CODE_OK { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error creating resource", status.NewErrorFromCode(createRes.Status.Code, "appprovider")) + return + } + + js, err := json.Marshal(createRes.ResourceInfo) + if err != nil { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error marshalling JSON response", err) + return + } + + w.Header().Set("Content-Type", "application/json") + if _, err = w.Write(js); err != nil { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error writing JSON response", err) + return + } +} + func (s *svc) handleList(w http.ResponseWriter, r *http.Request) { ctx := r.Context() client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc) diff --git a/pkg/app/app.go b/pkg/app/app.go index f99dcef71f..e811379ca8 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -38,8 +38,9 @@ type Registry interface { } // Provider is the interface that application providers implement -// for providing the URL of the app which will serve the requested resource. +// for interacting with external apps that serve the requested resource. type Provider interface { + CreateFile(ctx context.Context, ref *provider.Reference, filename string, template string) (*provider.ResourceInfo, error) GetAppURL(ctx context.Context, resource *provider.ResourceInfo, viewMode appprovider.OpenInAppRequest_ViewMode, token string) (*appprovider.OpenInAppURL, error) GetAppProviderInfo(ctx context.Context) (*registry.ProviderInfo, error) } diff --git a/pkg/app/provider/demo/demo.go b/pkg/app/provider/demo/demo.go index 254dd868ae..02ab40c121 100644 --- a/pkg/app/provider/demo/demo.go +++ b/pkg/app/provider/demo/demo.go @@ -21,13 +21,14 @@ package demo import ( "context" "fmt" - - "github.com/cs3org/reva/pkg/app" + "path" appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/app" "github.com/cs3org/reva/pkg/app/provider/registry" + uuid "github.com/google/uuid" "github.com/mitchellh/mapstructure" ) @@ -39,6 +40,17 @@ type demoProvider struct { iframeUIProvider string } +func (p *demoProvider) CreateFile(ctx context.Context, ref *provider.Reference, filename string, template string) (*provider.ResourceInfo, error) { + return &provider.ResourceInfo{ + Id: &provider.ResourceId{ + StorageId: "/", + OpaqueId: uuid.New().String(), + }, + Type: provider.ResourceType_RESOURCE_TYPE_FILE, + Path: path.Join(ref.GetPath(), filename), + }, nil +} + func (p *demoProvider) GetAppURL(ctx context.Context, resource *provider.ResourceInfo, viewMode appprovider.OpenInAppRequest_ViewMode, token string) (*appprovider.OpenInAppURL, error) { url := fmt.Sprintf("