Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[draft] [server] Video file preview #3539

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions server/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ tmp/**
museum.yaml
bin/**
data/
__debug_bin*
2 changes: 1 addition & 1 deletion server/cmd/museum/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func main() {
fileRepo := &repo.FileRepository{DB: db, S3Config: s3Config, QueueRepo: queueRepo,
ObjectRepo: objectRepo, ObjectCleanupRepo: objectCleanupRepo,
ObjectCopiesRepo: objectCopiesRepo, UsageRepo: usageRepo}
fileDataRepo := &fileDataRepo.Repository{DB: db}
fileDataRepo := &fileDataRepo.Repository{DB: db, ObjectCleanupRepo: objectCleanupRepo}
familyRepo := &repo.FamilyRepository{DB: db}
trashRepo := &repo.TrashRepository{DB: db, ObjectRepo: objectRepo, FileRepo: fileRepo, QueueRepo: queueRepo}
publicCollectionRepo := repo.NewPublicCollectionRepository(db, viper.GetString("apps.public-albums"))
Expand Down
8 changes: 8 additions & 0 deletions server/ente/base/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ func NewID(prefix string) (*string, error) {
return &result, nil
}

func MustNewID(prefix string) string {
id, err := NewID(prefix)
if err != nil {
panic(err)
}
return *id
}

func ServerReqID() string {
// Generate a nanoid with a custom alphabet and length of 22
id, err := NewID("ser")
Expand Down
22 changes: 13 additions & 9 deletions server/ente/filedata/filedata.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package filedata

import (
"fmt"

"github.com/ente-io/museum/ente"
)

Expand Down Expand Up @@ -61,6 +62,7 @@ type S3FileMetadata struct {
type GetPreviewURLRequest struct {
FileID int64 `form:"fileID" binding:"required"`
Type ente.ObjectType `form:"type" binding:"required"`
Suffix *string `form:"suffix"`
}

func (g *GetPreviewURLRequest) Validate() error {
Expand All @@ -75,6 +77,11 @@ type PreviewUploadUrlRequest struct {
Type ente.ObjectType `form:"type" binding:"required"`
}

type PreviewUploadUrl struct {
Id string `json:"id" binding:"required"`
Url string `json:"url" binding:"required"`
}

func (g *PreviewUploadUrlRequest) Validate() error {
if g.Type != ente.PreviewVideo && g.Type != ente.PreviewImage {
return ente.NewBadRequestWithMessage(fmt.Sprintf("unsupported object type %s", g.Type))
Expand All @@ -90,6 +97,8 @@ type Row struct {
// If a file type has multiple objects, then the size is the sum of all the objects.
Size int64
LatestBucket string
ObjectID *string
ObjectNonce *string
ReplicatedBuckets []string
DeleteFromBuckets []string
InflightReplicas []string
Expand All @@ -102,21 +111,16 @@ type Row struct {

// S3FileMetadataObjectKey returns the object key for the metadata stored in the S3 bucket.
func (r *Row) S3FileMetadataObjectKey() string {
if r.Type == ente.MlData {
return derivedMetaPath(r.FileID, r.UserID)
}
if r.Type == ente.PreviewVideo {
return previewVideoPlaylist(r.FileID, r.UserID)
if r.Type == ente.MlData || r.Type == ente.PreviewVideo {
return ObjectMedata(r.FileID, r.UserID, r.Type, r.ObjectID)
}
panic(fmt.Sprintf("S3FileMetadata should not be written for %s type", r.Type))
}

// GetS3FileObjectKey returns the object key for the file data stored in the S3 bucket.
func (r *Row) GetS3FileObjectKey() string {
if r.Type == ente.PreviewVideo {
return previewVideoPath(r.FileID, r.UserID)
} else if r.Type == ente.PreviewImage {
return previewImagePath(r.FileID, r.UserID)
if r.Type == ente.PreviewVideo || r.Type == ente.PreviewImage {
return ObjectKey(r.FileID, r.UserID, r.Type, r.ObjectID)
}
panic(fmt.Sprintf("unsupported object type %s", r.Type))
}
37 changes: 22 additions & 15 deletions server/ente/filedata/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package filedata

import (
"fmt"

"github.com/ente-io/museum/ente"
"github.com/ente-io/museum/ente/base"
)

// BasePrefix returns the base prefix for all objects related to a file. To check if the file data is deleted,
Expand All @@ -13,39 +15,44 @@ func BasePrefix(fileID int64, ownerID int64) string {

func AllObjects(fileID int64, ownerID int64, oType ente.ObjectType) []string {
switch oType {
case ente.PreviewVideo:
return []string{previewVideoPath(fileID, ownerID), previewVideoPlaylist(fileID, ownerID)}
case ente.MlData:
return []string{derivedMetaPath(fileID, ownerID)}
case ente.PreviewImage:
return []string{previewImagePath(fileID, ownerID)}

default:
// throw panic saying current object type is not supported
panic(fmt.Sprintf("object type %s is not supported", oType))
}
}

func PreviewUrl(fileID int64, ownerID int64, oType ente.ObjectType) string {
func ObjectKey(fileID int64, ownerID int64, oType ente.ObjectType, id *string) string {
switch oType {
case ente.PreviewVideo:
return previewVideoPath(fileID, ownerID)
return fmt.Sprintf("%s%s/%s", BasePrefix(fileID, ownerID), string(oType), *id)
case ente.PreviewImage:
return previewImagePath(fileID, ownerID)
return fmt.Sprintf("%s%s/%s", BasePrefix(fileID, ownerID), string(oType), *id)
default:
panic(fmt.Sprintf("object type %s is not supported", oType))
}
}

func previewVideoPath(fileID int64, ownerID int64) string {
return fmt.Sprintf("%s%s", BasePrefix(fileID, ownerID), string(ente.PreviewVideo))
}

func previewVideoPlaylist(fileID int64, ownerID int64) string {
return fmt.Sprintf("%s%s", previewVideoPath(fileID, ownerID), "_playlist.m3u8")
func ObjectMedata(fileID int64, ownerID int64, oType ente.ObjectType, id *string) string {
switch oType {
case ente.PreviewVideo:
return fmt.Sprintf("%s_playlist", ObjectKey(fileID, ownerID, oType, id))
case ente.MlData:
return fmt.Sprintf("%s%s", BasePrefix(fileID, ownerID), string(oType))
default:
panic(fmt.Sprintf("ObjectMetadata not supported for type %s", string(oType)))
}
}

func previewImagePath(fileID int64, ownerID int64) string {
return fmt.Sprintf("%s%s", BasePrefix(fileID, ownerID), string(ente.PreviewImage))
func NewUploadID(oType ente.ObjectType) string {
if oType == ente.PreviewVideo {
return base.MustNewID("pv")
} else if oType == ente.PreviewImage {
return base.MustNewID("pi")
}
panic(fmt.Sprintf("object type %s is not supported", oType))
}

func derivedMetaPath(fileID int64, ownerID int64) string {
Expand Down
40 changes: 2 additions & 38 deletions server/ente/filedata/putfiledata.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,21 @@ type PutFileDataRequest struct {
Type ente.ObjectType `json:"type" binding:"required"`
EncryptedData *string `json:"encryptedData,omitempty"`
DecryptionHeader *string `json:"decryptionHeader,omitempty"`
// ObjectKey is the key of the object in the S3 bucket. This is needed while putting the object in the S3 bucket.
ObjectKey *string `json:"objectKey,omitempty"`
// size of the object that is being uploaded. This helps in checking the size of the object that is being uploaded.
ObjectSize *int64 `json:"objectSize,omitempty"`
Version *int `json:"version,omitempty"`
Version *int `json:"version,omitempty"`
}

func (r PutFileDataRequest) isEncDataPresent() bool {
return r.EncryptedData != nil && r.DecryptionHeader != nil && *r.EncryptedData != "" && *r.DecryptionHeader != ""
}

func (r PutFileDataRequest) isObjectDataPresent() bool {
return r.ObjectKey != nil && *r.ObjectKey != "" && r.ObjectSize != nil && *r.ObjectSize > 0
}

func (r PutFileDataRequest) Validate() error {
switch r.Type {
case ente.PreviewVideo:
if !r.isEncDataPresent() || !r.isObjectDataPresent() {
return ente.NewBadRequestWithMessage("object and metadata are required")
}
case ente.PreviewImage:
if !r.isObjectDataPresent() || r.isEncDataPresent() {
return ente.NewBadRequestWithMessage("object (only) data is required for preview image")
}
case ente.MlData:
if !r.isEncDataPresent() || r.isObjectDataPresent() {
if !r.isEncDataPresent() {
return ente.NewBadRequestWithMessage("encryptedData and decryptionHeader (only) are required for derived meta")
}
default:
return ente.NewBadRequestWithMessage(fmt.Sprintf("invalid object type %s", r.Type))
}
return nil
}

func (r PutFileDataRequest) S3FileMetadataObjectKey(ownerID int64) string {
if r.Type == ente.MlData {
return derivedMetaPath(r.FileID, ownerID)
}
if r.Type == ente.PreviewVideo {
return previewVideoPlaylist(r.FileID, ownerID)
}
panic(fmt.Sprintf("S3FileMetadata should not be written for %s type", r.Type))
}

func (r PutFileDataRequest) S3FileObjectKey(ownerID int64) string {
if r.Type == ente.PreviewVideo {
return previewVideoPath(r.FileID, ownerID)
}
if r.Type == ente.PreviewImage {
return previewImagePath(r.FileID, ownerID)
}
panic(fmt.Sprintf("S3FileObjectKey should not be written for %s type", r.Type))
}
22 changes: 22 additions & 0 deletions server/ente/filedata/video.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package filedata

import "github.com/ente-io/museum/ente"

type VidPreviewRequest struct {
FileID int64 `json:"fileID" binding:"required"`
ObjectID string `json:"objectID" binding:"required"`
ObjectNonce string `json:"objectNonce" binding:"required"`
ObjectSize int64 `json:"objectSize" binding:"required"`
Playlist string `json:"playlist" binding:"required"`
PlayListNonce string `json:"playListNonce" binding:"required"`
}

func (r VidPreviewRequest) Validate() error {
if r.Playlist == "" || r.PlayListNonce == "" {
return ente.NewBadRequestWithMessage("playlist and playListNonce are required for preview video")
}
if r.ObjectNonce == "" {
return ente.NewBadRequestWithMessage("objectNonce is required for preview video")
}
return nil
}
3 changes: 3 additions & 0 deletions server/migrations/92_file_data_preview.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE file_data
DROP COLUMN obj_id,
DROP COLUMN obj_nonce;
7 changes: 7 additions & 0 deletions server/migrations/92_file_data_preview.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ALTER TYPE OBJECT_TYPE ADD VALUE 'vid_preview';
ALTER TYPE OBJECT_TYPE ADD VALUE 'img_preview';
ALTER TABLE file_data
ADD COLUMN obj_id TEXT,
ADD COLUMN obj_nonce TEXT;


10 changes: 4 additions & 6 deletions server/pkg/api/file_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (h *FileHandler) PutFileData(ctx *gin.Context) {
version := 1
reqInt.Version = &version
}
err := h.FileDataCtrl.InsertOrUpdate(ctx, &req)
err := h.FileDataCtrl.InsertOrUpdateMetadata(ctx, &req)
if err != nil {
handler.Error(ctx, err)

Expand Down Expand Up @@ -66,18 +66,16 @@ func (h *FileHandler) GetFileData(ctx *gin.Context) {

func (h *FileHandler) GetPreviewUploadURL(c *gin.Context) {
var request fileData.PreviewUploadUrlRequest
if err := c.ShouldBindJSON(&request); err != nil {
if err := c.ShouldBindQuery(&request); err != nil {
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, fmt.Sprintf("Request binding failed %s", err)))
return
}
url, err := h.FileDataCtrl.PreviewUploadURL(c, request)
resp, err := h.FileDataCtrl.PreviewUploadURL(c, request)
if err != nil {
handler.Error(c, stacktrace.Propagate(err, ""))
return
}
c.JSON(http.StatusOK, gin.H{
"url": url,
})
c.JSON(http.StatusOK, resp)
}

func (h *FileHandler) GetPreviewURL(c *gin.Context) {
Expand Down
17 changes: 3 additions & 14 deletions server/pkg/controller/filedata/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"github.com/ente-io/stacktrace"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"strings"
"sync"
gTime "time"
)
Expand Down Expand Up @@ -75,7 +74,7 @@ func New(repo *fileDataRepo.Repository,
}
}

func (c *Controller) InsertOrUpdate(ctx *gin.Context, req *fileData.PutFileDataRequest) error {
func (c *Controller) InsertOrUpdateMetadata(ctx *gin.Context, req *fileData.PutFileDataRequest) error {
if err := req.Validate(); err != nil {
return stacktrace.Propagate(err, "validation failed")
}
Expand All @@ -84,22 +83,12 @@ func (c *Controller) InsertOrUpdate(ctx *gin.Context, req *fileData.PutFileDataR
if err != nil {
return stacktrace.Propagate(err, "")
}
if req.Type != ente.MlData && req.Type != ente.PreviewVideo {
if req.Type != ente.MlData {
return stacktrace.Propagate(ente.NewBadRequestWithMessage("unsupported object type "+string(req.Type)), "")
}
fileOwnerID := userID
bucketID := c.S3Config.GetBucketID(req.Type)
if req.Type == ente.PreviewVideo {
fileObjectKey := req.S3FileObjectKey(fileOwnerID)
if !strings.Contains(*req.ObjectKey, fileObjectKey) {
return stacktrace.Propagate(ente.NewBadRequestWithMessage("objectKey should contain the file object key"), "")
}
err = c.copyObject(*req.ObjectKey, fileObjectKey, bucketID)
if err != nil {
return err
}
}
objectKey := req.S3FileMetadataObjectKey(fileOwnerID)
objectKey := fileData.ObjectMedata(req.FileID, fileOwnerID, req.Type, nil)
obj := fileData.S3FileMetadata{
Version: *req.Version,
EncryptedData: *req.EncryptedData,
Expand Down
4 changes: 4 additions & 0 deletions server/pkg/controller/filedata/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/ente-io/museum/ente"
"github.com/ente-io/museum/ente/filedata"
fileDataRepo "github.com/ente-io/museum/pkg/repo/filedata"
enteTime "github.com/ente-io/museum/pkg/utils/time"
Expand Down Expand Up @@ -79,6 +80,9 @@ func (c *Controller) deleteFileRow(fileDataRow filedata.Row) error {
panic(fmt.Sprintf("file %d does not belong to user %d", fileID, ownerID))
}
ctxLogger := log.WithField("file_id", fileDataRow.DeleteFromBuckets).WithField("type", fileDataRow.Type).WithField("user_id", fileDataRow.UserID)
if fileDataRow.Type != ente.MlData {
panic(fmt.Sprintf("unsupported object type for filedata deletion %s", fileDataRow.Type))
}
objectKeys := filedata.AllObjects(fileID, ownerID, fileDataRow.Type)
bucketColumnMap, err := getMapOfBucketItToColumn(fileDataRow)
if err != nil {
Expand Down
11 changes: 7 additions & 4 deletions server/pkg/controller/filedata/preview_files.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package filedata

import (
"fmt"
"github.com/ente-io/museum/ente"
"github.com/ente-io/museum/ente/filedata"
"github.com/ente-io/museum/pkg/utils/auth"
Expand Down Expand Up @@ -31,7 +30,7 @@ func (c *Controller) GetPreviewUrl(ctx *gin.Context, request filedata.GetPreview
return &enteUrl.URL, nil
}

func (c *Controller) PreviewUploadURL(ctx *gin.Context, request filedata.PreviewUploadUrlRequest) (*string, error) {
func (c *Controller) PreviewUploadURL(ctx *gin.Context, request filedata.PreviewUploadUrlRequest) (*filedata.PreviewUploadUrl, error) {
if err := request.Validate(); err != nil {
return nil, err
}
Expand All @@ -43,12 +42,16 @@ func (c *Controller) PreviewUploadURL(ctx *gin.Context, request filedata.Preview
if err != nil {
return nil, stacktrace.Propagate(err, "")
}
id := filedata.NewUploadID(request.Type)
// note: instead of the final url, give a temp url for upload purpose.
uploadUrl := fmt.Sprintf("%s_temp_upload", filedata.PreviewUrl(request.FileID, fileOwnerID, request.Type))
uploadUrl := filedata.ObjectKey(request.FileID, fileOwnerID, request.Type, &id)
bucketID := c.S3Config.GetBucketID(request.Type)
enteUrl, err := c.getUploadURL(bucketID, uploadUrl)
if err != nil {
return nil, stacktrace.Propagate(err, "")
}
return &enteUrl.URL, nil
return &filedata.PreviewUploadUrl{
Id: id,
Url: enteUrl.URL,
}, nil
}
Loading
Loading