diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go index 6a715ad142..167f8b1b62 100644 --- a/services/graph/pkg/service/v0/driveitems.go +++ b/services/graph/pkg/service/v0/driveitems.go @@ -45,7 +45,10 @@ const ( var shareTypesFieldMask = &fieldmaskpb.FieldMask{Paths: []string{"share-types"}} // opt-in driveItem relations, returned only when requested via $expand -const _expandChildren = "children" +const ( + _expandChildren = "children" + _expandThumbnails = "thumbnails" +) func odataListContains(r *http.Request, parameter, value string) bool { for _, values := range r.URL.Query()[parameter] { @@ -253,6 +256,7 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) { errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error()) return } + g.setDriveItemsThumbnails(r, files, lRes.GetInfos()) if driveItemPropertySelected(r, _selectAllowedValues) { for i, info := range lRes.GetInfos() { @@ -346,6 +350,10 @@ func (g Graph) GetDriveItem(w http.ResponseWriter, r *http.Request) { driveItem.LibreGraphShareTypes = shareTypesOf(res.GetInfo(), g.listLinkShares(ctx, infos)) } + if driveItemRelationExpanded(r, _expandThumbnails) { + setDriveItemThumbnails(driveItem, res.GetInfo(), g.config.Commons.OpenCloudURL) + } + render.Status(r, http.StatusOK) render.JSON(w, r, &driveItem) } @@ -432,6 +440,8 @@ func (g Graph) listDriveItemChildren(w http.ResponseWriter, r *http.Request, dri g.addShareTypes(r.Context(), files, res.GetInfos()) } + g.setDriveItemsThumbnails(r, files, res.GetInfos()) + return files, true } diff --git a/services/graph/pkg/service/v0/driveitems_test.go b/services/graph/pkg/service/v0/driveitems_test.go index f66bbe9425..42e698a096 100644 --- a/services/graph/pkg/service/v0/driveitems_test.go +++ b/services/graph/pkg/service/v0/driveitems_test.go @@ -208,6 +208,40 @@ var _ = Describe("Driveitems", func() { Expect(res.Value[0].LibreGraphPermissionsActionsAllowedValues).To(BeNil()) }) + It("returns the thumbnails when requested via $expand", func() { + cfg.Commons.OpenCloudURL = "https://cloud.test" + gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: []*provider.StorageSpace{{Owner: currentUser, Root: &provider.ResourceId{}}}, + }, nil) + gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{ + Status: status.NewOK(ctx), + Infos: []*provider.ResourceInfo{ + { + Type: provider.ResourceType_RESOURCE_TYPE_FILE, + Id: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"}, + MimeType: "image/jpeg", + Mtime: utils.TimeToTS(time.Now()), + }, + }, + }, nil) + r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/drive/root/children?$expand=thumbnails", nil) + r = r.WithContext(revactx.ContextSetUser(ctx, currentUser)) + svc.GetRootDriveChildren(rr, r) + Expect(rr.Code).To(Equal(http.StatusOK)) + data, err := io.ReadAll(rr.Body) + Expect(err).ToNot(HaveOccurred()) + + res := itemsList{} + Expect(json.Unmarshal(data, &res)).To(Succeed()) + Expect(len(res.Value)).To(Equal(1)) + Expect(res.Value[0].Thumbnails).To(HaveLen(1)) + Expect(res.Value[0].Thumbnails[0].Small.GetUrl()).To(Equal( + "https://cloud.test/dav/spaces/storageid$spaceid!opaqueid" + + "?scalingup=0&preview=1&processor=thumbnail&x=36&y=36", + )) + }) + It("returns the allowed actions when requested via $select", func() { gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{ Status: status.NewOK(ctx), @@ -248,6 +282,7 @@ var _ = Describe("Driveitems", func() { Describe("GetDriveItem", func() { var ( folderInfo *provider.ResourceInfo + childInfo *provider.ResourceInfo mtime = time.Now() ) @@ -281,16 +316,15 @@ var _ = Describe("Driveitems", func() { Status: status.NewOK(ctx), Info: folderInfo, }, nil) + childInfo = &provider.ResourceInfo{ + Type: provider.ResourceType_RESOURCE_TYPE_FILE, + Id: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"}, + Etag: "etag", + Mtime: utils.TimeToTS(mtime), + } gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{ Status: status.NewOK(ctx), - Infos: []*provider.ResourceInfo{ - { - Type: provider.ResourceType_RESOURCE_TYPE_FILE, - Id: &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"}, - Etag: "etag", - Mtime: utils.TimeToTS(mtime), - }, - }, + Infos: []*provider.ResourceInfo{childInfo}, }, nil) }) @@ -312,6 +346,49 @@ var _ = Describe("Driveitems", func() { Expect(getItem(newRequest("?$expand=children")).Children).To(BeNil()) gatewayClient.AssertNotCalled(GinkgoT(), "ListContainer", mock.Anything, mock.Anything) }) + + Context("$expand=thumbnails", func() { + const previewURL = "https://cloud.test/dav/spaces/storageid$spaceid!nodeid" + + "?scalingup=0&preview=1&processor=thumbnail" + + BeforeEach(func() { + cfg.Commons.OpenCloudURL = "https://cloud.test" + folderInfo.Type = provider.ResourceType_RESOURCE_TYPE_FILE + folderInfo.MimeType = "image/jpeg" + }) + + It("leaves thumbnails unset without $expand", func() { + Expect(getItem(newRequest("")).Thumbnails).To(BeNil()) + }) + + It("returns the thumbnail urls when requested", func() { + thumbnails := getItem(newRequest("?$expand=thumbnails")).Thumbnails + + Expect(thumbnails).To(HaveLen(1)) + Expect(thumbnails[0].Small.GetUrl()).To(Equal(previewURL + "&x=36&y=36")) + Expect(thumbnails[0].Medium.GetUrl()).To(Equal(previewURL + "&x=48&y=48")) + Expect(thumbnails[0].Large.GetUrl()).To(Equal(previewURL + "&x=96&y=96")) + }) + + It("leaves thumbnails unset for a mime type the thumbnailer cannot render", func() { + folderInfo.MimeType = "application/zip" + + Expect(getItem(newRequest("?$expand=thumbnails")).Thumbnails).To(BeNil()) + }) + + It("adds them to expanded children as well", func() { + folderInfo.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER + folderInfo.MimeType = "" + childInfo.MimeType = "image/jpeg" + + item := getItem(newRequest("?$expand=children,thumbnails")) + + // a folder has no preview of its own + Expect(item.Thumbnails).To(BeNil()) + Expect(item.Children).To(HaveLen(1)) + Expect(item.Children[0].Thumbnails).To(HaveLen(1)) + }) + }) }) Describe("GetDriveItem $expand=children error", func() { diff --git a/services/graph/pkg/service/v0/sharedbyme.go b/services/graph/pkg/service/v0/sharedbyme.go index e2b40da15b..cc61df8b75 100644 --- a/services/graph/pkg/service/v0/sharedbyme.go +++ b/services/graph/pkg/service/v0/sharedbyme.go @@ -1,14 +1,12 @@ package svc import ( - "fmt" "net/http" "strings" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/go-chi/render" libregraph "github.com/opencloud-eu/libre-graph-api-go" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail" "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" ) @@ -44,30 +42,8 @@ func (g Graph) GetSharedByMe(w http.ResponseWriter, r *http.Request) { expandThumbnails := strings.Contains(expand, "thumbnails") if expandThumbnails { for k, item := range driveItems { - mt := item.GetFile().MimeType - if mt == nil { - continue - } - - _, match := thumbnail.SupportedMimeTypes[*mt] - if match { - baseUrl := fmt.Sprintf("%s/dav/spaces/%s?scalingup=0&preview=1&processor=thumbnail", - g.config.Commons.OpenCloudURL, - item.GetId()) - smallUrl := baseUrl + "&x=36&y=36" - mediumUrl := baseUrl + "&x=48&y=48" - largeUrl := baseUrl + "&x=96&y=96" - - item.SetThumbnails([]libregraph.ThumbnailSet{ - { - Small: &libregraph.Thumbnail{Url: &smallUrl}, - Medium: &libregraph.Thumbnail{Url: &mediumUrl}, - Large: &libregraph.Thumbnail{Url: &largeUrl}, - }, - }) - - driveItems[k] = item // assign modified item back to the map - } + setShareThumbnails(&item, item.GetId(), g.config.Commons.OpenCloudURL) + driveItems[k] = item } } diff --git a/services/graph/pkg/service/v0/sharedwithme.go b/services/graph/pkg/service/v0/sharedwithme.go index c9d1ca92cf..87aa96a887 100644 --- a/services/graph/pkg/service/v0/sharedwithme.go +++ b/services/graph/pkg/service/v0/sharedwithme.go @@ -2,7 +2,6 @@ package svc import ( "context" - "fmt" "net/http" "strings" @@ -13,7 +12,6 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/share" "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail" ) // ListSharedWithMe lists the files shared with the current user. @@ -73,30 +71,8 @@ func (g Graph) listSharedWithMe(ctx context.Context, expandThumbnails bool) ([]l if expandThumbnails { for k, item := range driveItems { - mt := item.GetFile().MimeType - if mt == nil { - continue - } - - _, match := thumbnail.SupportedMimeTypes[*mt] - if match { - baseUrl := fmt.Sprintf("%s/dav/spaces/%s?scalingup=0&preview=1&processor=thumbnail", - g.config.Commons.OpenCloudURL, - item.RemoteItem.GetId()) - smallUrl := baseUrl + "&x=36&y=36" - mediumUrl := baseUrl + "&x=48&y=48" - largeUrl := baseUrl + "&x=96&y=96" - - item.SetThumbnails([]libregraph.ThumbnailSet{ - { - Small: &libregraph.Thumbnail{Url: &smallUrl}, - Medium: &libregraph.Thumbnail{Url: &mediumUrl}, - Large: &libregraph.Thumbnail{Url: &largeUrl}, - }, - }) - - driveItems[k] = item // assign modified item back to the map - } + setShareThumbnails(&item, item.RemoteItem.GetId(), g.config.Commons.OpenCloudURL) + driveItems[k] = item } } diff --git a/services/graph/pkg/service/v0/thumbnails.go b/services/graph/pkg/service/v0/thumbnails.go new file mode 100644 index 0000000000..75d28bd3a7 --- /dev/null +++ b/services/graph/pkg/service/v0/thumbnails.go @@ -0,0 +1,68 @@ +package svc + +import ( + "fmt" + "net/http" + + 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/opencloud/services/thumbnails/pkg/thumbnail" +) + +const ( + thumbnailBoxSmall = 36 + thumbnailBoxMedium = 48 + thumbnailBoxLarge = 96 +) + +func (g Graph) setDriveItemsThumbnails(r *http.Request, items []libregraph.DriveItem, infos []*provider.ResourceInfo) { + if !driveItemRelationExpanded(r, _expandThumbnails) { + return + } + for i := range items { + if i < len(infos) { + setDriveItemThumbnails(&items[i], infos[i], g.config.Commons.OpenCloudURL) + } + } +} + +func setDriveItemThumbnails(item *libregraph.DriveItem, res *provider.ResourceInfo, baseURL string) { + if set := previewThumbnailSet(res, baseURL); set != nil { + item.SetThumbnails([]libregraph.ThumbnailSet{*set}) + } +} + +// previewThumbnailSet returns nil when the thumbnailer cannot render the resource. +func previewThumbnailSet(res *provider.ResourceInfo, baseURL string) *libregraph.ThumbnailSet { + if !thumbnail.IsMimeTypeSupported(res.GetMimeType()) { + return nil + } + return thumbnailSetFor(baseURL, storagespace.FormatResourceID(res.GetId())) +} + +// thumbnailSetFor builds the urls of the WebDAV preview endpoint. +func thumbnailSetFor(baseURL, itemID string) *libregraph.ThumbnailSet { + base := fmt.Sprintf("%s/dav/spaces/%s?scalingup=0&preview=1&processor=thumbnail", baseURL, itemID) + return &libregraph.ThumbnailSet{ + Small: previewThumbnail(base, thumbnailBoxSmall), + Medium: previewThumbnail(base, thumbnailBoxMedium), + Large: previewThumbnail(base, thumbnailBoxLarge), + } +} + +func previewThumbnail(base string, box int32) *libregraph.Thumbnail { + url := fmt.Sprintf("%s&x=%d&y=%d", base, box, box) + return &libregraph.Thumbnail{Url: &url} +} + +// setShareThumbnails works off the driveItem, the share listings have no resource +// info. The id comes separately, a received share carries it on its remote item. +func setShareThumbnails(item *libregraph.DriveItem, itemID, baseURL string) { + mimeType := item.GetFile().MimeType + if itemID == "" || mimeType == nil || !thumbnail.IsMimeTypeSupported(*mimeType) { + return + } + item.SetThumbnails([]libregraph.ThumbnailSet{*thumbnailSetFor(baseURL, itemID)}) +}