// Copyright 2017-2018 New Vector Ltd // Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosmosdb import ( "context" "fmt" "time" "github.com/matrix-org/dendrite/internal/cosmosdbapi" "github.com/matrix-org/dendrite/internal/cosmosdbutil" "github.com/matrix-org/dendrite/mediaapi/types" "github.com/matrix-org/gomatrixserverlib" ) // const thumbnailSchema = ` // -- The mediaapi_thumbnail table holds metadata for each thumbnail file stored and accessible to the local server, // -- the actual file is stored separately. // CREATE TABLE IF NOT EXISTS mediaapi_thumbnail ( // media_id TEXT NOT NULL, // media_origin TEXT NOT NULL, // content_type TEXT NOT NULL, // file_size_bytes INTEGER NOT NULL, // creation_ts INTEGER NOT NULL, // width INTEGER NOT NULL, // height INTEGER NOT NULL, // resize_method TEXT NOT NULL // ); // CREATE UNIQUE INDEX IF NOT EXISTS mediaapi_thumbnail_index ON mediaapi_thumbnail (media_id, media_origin, width, height, resize_method); // ` type thumbnailCosmos struct { MediaID string `json:"media_id"` MediaOrigin string `json:"media_origin"` ContentType string `json:"content_type"` FileSizeBytes int64 `json:"file_size_bytes"` CreationTimestamp int64 `json:"creation_ts"` Width int64 `json:"width"` Height int64 `json:"height"` ResizeMethod string `json:"resize_method"` } type thumbnailCosmosData struct { cosmosdbapi.CosmosDocument Thumbnail thumbnailCosmos `json:"mx_mediaapi_thumbnail"` } // const insertThumbnailSQL = ` // INSERT INTO mediaapi_thumbnail (media_id, media_origin, content_type, file_size_bytes, creation_ts, width, height, resize_method) // VALUES ($1, $2, $3, $4, $5, $6, $7, $8) // ` // Note: this selects one specific thumbnail // const selectThumbnailSQL = ` // SELECT content_type, file_size_bytes, creation_ts FROM mediaapi_thumbnail WHERE media_id = $1 AND media_origin = $2 AND width = $3 AND height = $4 AND resize_method = $5 // ` // Note: this selects all thumbnails for a media_origin and media_id // SELECT content_type, file_size_bytes, creation_ts, width, height, resize_method FROM mediaapi_thumbnail WHERE media_id = $1 AND media_origin = $2 const selectThumbnailsSQL = "" + "select * from c where c._cn = @x1 " + "and c.mx_mediaapi_thumbnail.media_id = @x2" + "and c.mx_mediaapi_thumbnail.media_origin = @x3" type thumbnailStatements struct { db *Database writer cosmosdbutil.Writer // insertThumbnailStmt *sql.Stmt // selectThumbnailStmt *sql.Stmt selectThumbnailsStmt string tableName string } func (s *thumbnailStatements) getCollectionName() string { return cosmosdbapi.GetCollectionName(s.db.databaseName, s.tableName) } func (s *thumbnailStatements) getPartitionKey(mediaId string) string { uniqueId := mediaId return cosmosdbapi.GetPartitionKeyByUniqueId(s.db.cosmosConfig.TenantName, s.getCollectionName(), uniqueId) } func getThumbnail(s *thumbnailStatements, ctx context.Context, pk string, docId string) (*thumbnailCosmosData, error) { response := thumbnailCosmosData{} err := cosmosdbapi.GetDocumentOrNil( s.db.connection, s.db.cosmosConfig, ctx, pk, docId, &response) if response.Id == "" { return nil, nil } return &response, err } func (s *thumbnailStatements) prepare(db *Database, writer cosmosdbutil.Writer) (err error) { s.db = db s.writer = writer s.tableName = "thumbnail" s.selectThumbnailsStmt = selectThumbnailsSQL return } func (s *thumbnailStatements) insertThumbnail( ctx context.Context, thumbnailMetadata *types.ThumbnailMetadata, ) error { thumbnailMetadata.MediaMetadata.CreationTimestamp = types.UnixMs(time.Now().UnixNano() / 1000000) // INSERT INTO mediaapi_thumbnail (media_id, media_origin, content_type, file_size_bytes, creation_ts, width, height, resize_method) // VALUES ($1, $2, $3, $4, $5, $6, $7, $8) // return s.writer.Do(s.db, nil, func(txn *sql.Tx) error { // stmt := sqlutil.TxStmt(txn, s.insertThumbnailStmt) // CREATE UNIQUE INDEX IF NOT EXISTS mediaapi_thumbnail_index ON mediaapi_thumbnail (media_id, media_origin, width, height, resize_method); docId := fmt.Sprintf("%s_%s_%d_%d_%s", thumbnailMetadata.MediaMetadata.MediaID, thumbnailMetadata.MediaMetadata.Origin, thumbnailMetadata.ThumbnailSize.Width, thumbnailMetadata.ThumbnailSize.Height, thumbnailMetadata.ThumbnailSize.ResizeMethod, ) cosmosDocId := cosmosdbapi.GetDocumentId(s.db.cosmosConfig.TenantName, s.getCollectionName(), docId) // _, err := stmt.ExecContext( // ctx, // thumbnailMetadata.MediaMetadata.MediaID, // thumbnailMetadata.MediaMetadata.Origin, // thumbnailMetadata.MediaMetadata.ContentType, // thumbnailMetadata.MediaMetadata.FileSizeBytes, // thumbnailMetadata.MediaMetadata.CreationTimestamp, // thumbnailMetadata.ThumbnailSize.Width, // thumbnailMetadata.ThumbnailSize.Height, // thumbnailMetadata.ThumbnailSize.ResizeMethod, // ) data := thumbnailCosmos{ MediaID: string(thumbnailMetadata.MediaMetadata.MediaID), MediaOrigin: string(thumbnailMetadata.MediaMetadata.Origin), ContentType: string(thumbnailMetadata.MediaMetadata.ContentType), FileSizeBytes: int64(thumbnailMetadata.MediaMetadata.FileSizeBytes), CreationTimestamp: int64(thumbnailMetadata.MediaMetadata.CreationTimestamp), Width: int64(thumbnailMetadata.ThumbnailSize.Width), Height: int64(thumbnailMetadata.ThumbnailSize.Height), ResizeMethod: string(thumbnailMetadata.ThumbnailSize.ResizeMethod), } dbData := &thumbnailCosmosData{ CosmosDocument: cosmosdbapi.GenerateDocument(s.getCollectionName(), s.db.cosmosConfig.TenantName, s.getPartitionKey(data.MediaID), cosmosDocId), Thumbnail: data, } var options = cosmosdbapi.GetCreateDocumentOptions(dbData.Pk) _, _, err := cosmosdbapi.GetClient(s.db.connection).CreateDocument( ctx, s.db.cosmosConfig.DatabaseName, s.db.cosmosConfig.ContainerName, &dbData, options) return err } func (s *thumbnailStatements) selectThumbnail( ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName, width, height int, resizeMethod string, ) (*types.ThumbnailMetadata, error) { thumbnailMetadata := types.ThumbnailMetadata{ MediaMetadata: &types.MediaMetadata{ MediaID: mediaID, Origin: mediaOrigin, }, ThumbnailSize: types.ThumbnailSize{ Width: width, Height: height, ResizeMethod: resizeMethod, }, } // SELECT content_type, file_size_bytes, creation_ts FROM mediaapi_thumbnail WHERE media_id = $1 AND media_origin = $2 AND width = $3 AND height = $4 AND resize_method = $5 // CREATE UNIQUE INDEX IF NOT EXISTS mediaapi_thumbnail_index ON mediaapi_thumbnail (media_id, media_origin, width, height, resize_method); docId := fmt.Sprintf("%s_%s_%d_%d_%s", mediaID, mediaOrigin, width, height, resizeMethod, ) cosmosDocId := cosmosdbapi.GetDocumentId(s.db.cosmosConfig.TenantName, s.getCollectionName(), docId) // row := sqlutil.TxStmt(txn, s.selectOutboundPeeksStmt).QueryRowContext(ctx, roomID) row, err := getThumbnail(s, ctx, s.getPartitionKey(string(mediaID)), cosmosDocId) if err != nil { return nil, err } if row == nil { return nil, nil } thumbnailMetadata.MediaMetadata.MediaID = types.MediaID(row.Thumbnail.MediaID) thumbnailMetadata.MediaMetadata.Origin = gomatrixserverlib.ServerName(row.Thumbnail.MediaOrigin) thumbnailMetadata.ThumbnailSize.Width = int(row.Thumbnail.Width) thumbnailMetadata.ThumbnailSize.Height = int(row.Thumbnail.Height) thumbnailMetadata.ThumbnailSize.ResizeMethod = row.Thumbnail.ResizeMethod thumbnailMetadata.MediaMetadata.ContentType = types.ContentType(row.Thumbnail.ContentType) thumbnailMetadata.MediaMetadata.FileSizeBytes = types.FileSizeBytes(row.Thumbnail.FileSizeBytes) thumbnailMetadata.MediaMetadata.CreationTimestamp = types.UnixMs(row.Thumbnail.CreationTimestamp) return &thumbnailMetadata, nil } func (s *thumbnailStatements) selectThumbnails( ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName, ) ([]*types.ThumbnailMetadata, error) { // SELECT content_type, file_size_bytes, creation_ts, width, height, resize_method FROM mediaapi_thumbnail WHERE media_id = $1 AND media_origin = $2 params := map[string]interface{}{ "@x1": s.getCollectionName(), "@x2": mediaID, "@x3": mediaOrigin, } // rows, err := s.selectThumbnailsStmt.QueryContext( // ctx, mediaID, mediaOrigin, // ) var rows []thumbnailCosmosData err := cosmosdbapi.PerformQuery(ctx, s.db.connection, s.db.cosmosConfig.DatabaseName, s.db.cosmosConfig.ContainerName, s.getPartitionKey(string(mediaID)), s.selectThumbnailsStmt, params, &rows) if err != nil { return nil, err } var thumbnails []*types.ThumbnailMetadata for _, item := range rows { thumbnailMetadata := types.ThumbnailMetadata{ MediaMetadata: &types.MediaMetadata{ MediaID: mediaID, Origin: mediaOrigin, }, } thumbnailMetadata.MediaMetadata.ContentType = types.ContentType(item.Thumbnail.ContentType) thumbnailMetadata.MediaMetadata.FileSizeBytes = types.FileSizeBytes(item.Thumbnail.FileSizeBytes) thumbnailMetadata.MediaMetadata.CreationTimestamp = types.UnixMs(item.Thumbnail.CreationTimestamp) thumbnailMetadata.ThumbnailSize.Width = int(item.Thumbnail.Width) thumbnailMetadata.ThumbnailSize.Height = int(item.Thumbnail.Height) thumbnailMetadata.ThumbnailSize.ResizeMethod = item.Thumbnail.ResizeMethod thumbnails = append(thumbnails, &thumbnailMetadata) } return thumbnails, err }