Skip to content

Commit

Permalink
Add audio facet to driveItems listings
Browse files Browse the repository at this point in the history
  • Loading branch information
dschmidt committed Oct 24, 2023
1 parent dd39c01 commit ec78fc5
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 38 deletions.
88 changes: 81 additions & 7 deletions services/graph/pkg/service/v0/driveitems.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"net/http"
"net/url"
"path"
"reflect"
"strconv"
"strings"
"time"

cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
Expand All @@ -17,6 +20,7 @@ import (
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode"
"golang.org/x/crypto/sha3"
)
Expand Down Expand Up @@ -87,7 +91,7 @@ func (g Graph) GetRootDriveChildren(w http.ResponseWriter, r *http.Request) {
return
}

files, err := formatDriveItems(lRes.Infos)
files, err := formatDriveItems(g.logger, lRes.Infos)
if err != nil {
g.logger.Error().Err(err).Msg("error encoding response as json")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
Expand Down Expand Up @@ -152,7 +156,7 @@ func (g Graph) GetDriveItem(w http.ResponseWriter, r *http.Request) {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, res.Status.Message)
return
}
driveItem, err := cs3ResourceToDriveItem(res.Info)
driveItem, err := cs3ResourceToDriveItem(g.logger, res.Info)
if err != nil {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
Expand Down Expand Up @@ -220,7 +224,7 @@ func (g Graph) GetDriveItemChildren(w http.ResponseWriter, r *http.Request) {
return
}

files, err := formatDriveItems(res.Infos)
files, err := formatDriveItems(g.logger, res.Infos)
if err != nil {
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
Expand All @@ -244,7 +248,7 @@ func (g Graph) getDriveItem(ctx context.Context, ref storageprovider.Reference)
refStr, _ := storagespace.FormatReference(&ref)
return nil, fmt.Errorf("could not stat %s: %s", refStr, res.Status.Message)
}
return cs3ResourceToDriveItem(res.Info)
return cs3ResourceToDriveItem(g.logger, res.Info)
}

func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.ResourceId, baseURL *url.URL) (*libregraph.RemoteItem, error) {
Expand Down Expand Up @@ -285,10 +289,10 @@ func (g Graph) getRemoteItem(ctx context.Context, root *storageprovider.Resource
return item, nil
}

func formatDriveItems(mds []*storageprovider.ResourceInfo) ([]*libregraph.DriveItem, error) {
func formatDriveItems(logger *log.Logger, mds []*storageprovider.ResourceInfo) ([]*libregraph.DriveItem, error) {
responses := make([]*libregraph.DriveItem, 0, len(mds))
for i := range mds {
res, err := cs3ResourceToDriveItem(mds[i])
res, err := cs3ResourceToDriveItem(logger, mds[i])
if err != nil {
return nil, err
}
Expand All @@ -302,7 +306,7 @@ func cs3TimestampToTime(t *types.Timestamp) time.Time {
return time.Unix(int64(t.Seconds), int64(t.Nanos))
}

func cs3ResourceToDriveItem(res *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) {
func cs3ResourceToDriveItem(logger *log.Logger, res *storageprovider.ResourceInfo) (*libregraph.DriveItem, error) {
size := new(int64)
*size = int64(res.Size) // TODO lurking overflow: make size of libregraph drive item use uint64

Expand Down Expand Up @@ -330,9 +334,79 @@ func cs3ResourceToDriveItem(res *storageprovider.ResourceInfo) (*libregraph.Driv
if res.Type == storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER {
driveItem.Folder = &libregraph.Folder{}
}

driveItem.Audio = cs3ResourceToDriveItemAudioFacet(logger, res)

return driveItem, nil
}

func cs3ResourceToDriveItemAudioFacet(logger *log.Logger, res *storageprovider.ResourceInfo) *libregraph.Audio {
if !strings.HasPrefix(res.MimeType, "audio/") {
return nil
}

k := res.ArbitraryMetadata.Metadata
if k == nil {
return nil
}

var audio = &libregraph.Audio{}
if ok := unmarshalStringMap(logger, audio, k, "libre.graph.audio."); ok {
return audio
}

return nil
}

func getFieldName(structField reflect.StructField) string {
tag := structField.Tag.Get("json")
if tag == "" {
return structField.Name
}

return strings.Split(tag, ",")[0]
}

func unmarshalStringMap(logger *log.Logger, out any, flatMap map[string]string, prefix string) bool {
nonEmpty := false
obj := reflect.ValueOf(out).Elem()
for i := 0; i < obj.NumField(); i++ {
field := obj.Field(i)
structField := obj.Type().Field(i)
mapKey := prefix + getFieldName(structField)

if value, ok := flatMap[mapKey]; ok {
if field.Kind() == reflect.Ptr {
newValue := reflect.New(field.Type().Elem())
var tmp any
var err error
switch t := newValue.Type().Elem().Kind(); t {
case reflect.String:
tmp = value
case reflect.Int32:
tmp, err = strconv.ParseInt(value, 10, 32)
case reflect.Int64:
tmp, err = strconv.ParseInt(value, 10, 64)
case reflect.Bool:
tmp, err = strconv.ParseBool(value)
default:
err = errors.New("unsupported type")
logger.Error().Err(err).Str("type", t.String()).Str("mapKey", mapKey).Msg("target field type for value of mapKey is not supported")
}
if err != nil {
logger.Error().Err(err).Str("mapKey", mapKey).Msg("unmarshalling failed")
continue
}
newValue.Elem().Set(reflect.ValueOf(tmp).Convert(field.Type().Elem()))
field.Set(newValue)
nonEmpty = true
}
}
}

return nonEmpty
}

func cs3ResourceToRemoteItem(res *storageprovider.ResourceInfo) (*libregraph.RemoteItem, error) {
size := new(int64)
*size = int64(res.Size) // TODO lurking overflow: make size of libregraph drive item use uint64
Expand Down
137 changes: 106 additions & 31 deletions services/graph/pkg/service/v0/driveitems_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,38 +240,113 @@ var _ = Describe("Driveitems", func() {
Expect(rr.Code).To(Equal(http.StatusInternalServerError))
})

It("succeeds", func() {
mtime := time.Now()
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),
Context("it succeeds", func() {
var (
r *http.Request
mtime = time.Now()
)

BeforeEach(func() {
r = httptest.NewRequest(http.MethodGet, "/graph/v1.0/drives/storageid$spaceid/items/storageid$spaceid!nodeid/children", nil)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "storageid$spaceid")
rctx.URLParams.Add("driveItemID", "storageid$spaceid!nodeid")
r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
})

assertItemsList := func(length int) itemsList {
svc.GetDriveItemChildren(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
data, err := io.ReadAll(rr.Body)
Expect(err).ToNot(HaveOccurred())

res := itemsList{}

err = json.Unmarshal(data, &res)
Expect(err).ToNot(HaveOccurred())

Expect(len(res.Value)).To(Equal(1))
Expect(res.Value[0].GetLastModifiedDateTime().Equal(mtime)).To(BeTrue())
Expect(res.Value[0].GetETag()).To(Equal("etag"))
Expect(res.Value[0].GetId()).To(Equal("storageid$spaceid!opaqueid"))
Expect(res.Value[0].GetId()).To(Equal("storageid$spaceid!opaqueid"))

return res
}

It("returns a generic file", func() {
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),
ArbitraryMetadata: nil,
},
},
},
}, nil)
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/drives/storageid$spaceid/items/storageid$spaceid!nodeid/children", nil)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("driveID", "storageid$spaceid")
rctx.URLParams.Add("driveItemID", "storageid$spaceid!nodeid")
r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
svc.GetDriveItemChildren(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
data, err := io.ReadAll(rr.Body)
Expect(err).ToNot(HaveOccurred())

res := itemsList{}

err = json.Unmarshal(data, &res)
Expect(err).ToNot(HaveOccurred())

Expect(len(res.Value)).To(Equal(1))
Expect(res.Value[0].GetLastModifiedDateTime().Equal(mtime)).To(BeTrue())
Expect(res.Value[0].GetETag()).To(Equal("etag"))
Expect(res.Value[0].GetId()).To(Equal("storageid$spaceid!opaqueid"))
}, nil)

res := assertItemsList(1)
Expect(res.Value[0].Audio).To(BeNil())
})

It("returns the audio facet if metadata is available", func() {
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),
MimeType: "audio/mpeg",
ArbitraryMetadata: &provider.ArbitraryMetadata{
Metadata: map[string]string{
"libre.graph.audio.album": "Some Album",
"libre.graph.audio.albumArtist": "Some AlbumArtist",
"libre.graph.audio.artist": "Some Artist",
"libre.graph.audio.bitrate": "192",
"libre.graph.audio.composers": "Some Composers",
"libre.graph.audio.copyright": "Some Copyright",
"libre.graph.audio.disc": "2",
"libre.graph.audio.discCount": "5",
"libre.graph.audio.duration": "225000",
"libre.graph.audio.genre": "Some Genre",
"libre.graph.audio.hasDrm": "false",
"libre.graph.audio.isVariableBitrate": "true",
"libre.graph.audio.title": "Some Title",
"libre.graph.audio.track": "6",
"libre.graph.audio.trackCount": "9",
"libre.graph.audio.year": "1994",
},
},
},
},
}, nil)

res := assertItemsList(1)
audio := res.Value[0].Audio

Expect(audio).ToNot(BeNil())
Expect(audio.GetAlbum()).To(Equal("Some Album"))
Expect(audio.GetAlbumArtist()).To(Equal("Some AlbumArtist"))
Expect(audio.GetArtist()).To(Equal("Some Artist"))
Expect(audio.GetBitrate()).To(Equal(int64(192)))
Expect(audio.GetComposers()).To(Equal("Some Composers"))
Expect(audio.GetCopyright()).To(Equal("Some Copyright"))
Expect(audio.GetDisc()).To(Equal(int32(2)))
Expect(audio.GetDiscCount()).To(Equal(int32(5)))
Expect(audio.GetDuration()).To(Equal(int64(225000)))
Expect(audio.GetGenre()).To(Equal("Some Genre"))
Expect(audio.GetHasDrm()).To(Equal(false))
Expect(audio.GetIsVariableBitrate()).To(Equal(true))
Expect(audio.GetTitle()).To(Equal("Some Title"))
Expect(audio.GetTrack()).To(Equal(int32(6)))
Expect(audio.GetTrackCount()).To(Equal(int32(9)))
Expect(audio.GetYear()).To(Equal(int32(1994)))
})
})
})
})

0 comments on commit ec78fc5

Please sign in to comment.