diff --git a/changelog/unreleased/appreg-mimetypes.md b/changelog/unreleased/appreg-mimetypes.md index 0083f3c0780..862f9621452 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 8dd446d704c..23b8106d471 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.OpenInAppResponse{ + 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 b834968ced5..b77f3a15ad5 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) { + p, 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 a632f50d154..a1d09a0c8a5 100644 --- a/internal/http/services/appprovider/appprovider.go +++ b/internal/http/services/appprovider/appprovider.go @@ -102,6 +102,8 @@ func (s *svc) Handler() http.Handler { s.handleList(w, r) case "open": s.handleOpen(w, r) + case "new": + s.handleNew(w, r) } }) } @@ -182,6 +184,48 @@ func (s *svc) handleOpen(w http.ResponseWriter, r *http.Request) { } } +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 := gateway.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 opening resource", err) + return + } + if createRes.Status.Code != rpc.Code_CODE_OK { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error opening resource information", status.NewErrorFromCode(openRes.Status.Code, "appprovider")) + return + } + + js, err := json.Marshal(createRes.resourceInfo.info) + 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 filterAppsByUserAgent(mimeTypes *appregistry.MimeTypeList, userAgent string) { ua := ua.Parse(userAgent) res := []*appregistry.MimeTypeInfo{} diff --git a/pkg/app/app.go b/pkg/app/app.go index f99dcef71f9..e811379ca8f 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 254dd868ae9..02ab40c1211 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("