diff --git a/keyserver/src/fetchers/upload-fetchers.js b/keyserver/src/fetchers/upload-fetchers.js index d647a0112..4b8c4bd62 100644 --- a/keyserver/src/fetchers/upload-fetchers.js +++ b/keyserver/src/fetchers/upload-fetchers.js @@ -1,201 +1,197 @@ // @flow import _keyBy from 'lodash/fp/keyBy'; import type { Media } from 'lib/types/media-types'; import type { MediaMessageServerDBContent } from 'lib/types/messages/media.js'; +import { getUploadIDsFromMediaMessageServerDBContents } from 'lib/types/messages/media.js'; import { ServerError } from 'lib/utils/errors'; import { dbQuery, SQL } from '../database/database'; import type { Viewer } from '../session/viewer'; import { getAndAssertCommAppURLFacts } from '../utils/urls'; type UploadInfo = { content: Buffer, mime: string, }; async function fetchUpload( viewer: Viewer, id: string, secret: string, ): Promise { const query = SQL` SELECT content, mime FROM uploads WHERE id = ${id} AND secret = ${secret} `; const [result] = await dbQuery(query); if (result.length === 0) { throw new ServerError('invalid_parameters'); } const [row] = result; const { content, mime } = row; return { content, mime }; } async function fetchUploadChunk( id: string, secret: string, pos: number, len: number, ): Promise { // We use pos + 1 because SQL is 1-indexed whereas js is 0-indexed const query = SQL` SELECT SUBSTRING(content, ${pos + 1}, ${len}) AS content, mime FROM uploads WHERE id = ${id} AND secret = ${secret} `; const [result] = await dbQuery(query); if (result.length === 0) { throw new ServerError('invalid_parameters'); } const [row] = result; const { content, mime } = row; return { content, mime, }; } // Returns total size in bytes. async function getUploadSize(id: string, secret: string): Promise { const query = SQL` SELECT LENGTH(content) AS length FROM uploads WHERE id = ${id} AND secret = ${secret} `; const [result] = await dbQuery(query); if (result.length === 0) { throw new ServerError('invalid_parameters'); } const [row] = result; const { length } = row; return length; } function getUploadURL(id: string, secret: string): string { const { baseDomain, basePath } = getAndAssertCommAppURLFacts(); return `${baseDomain}${basePath}upload/${id}/${secret}`; } function mediaFromRow(row: Object): Media { const uploadExtra = JSON.parse(row.uploadExtra); const { width, height, loop } = uploadExtra; const { uploadType: type, uploadSecret: secret } = row; const id = row.uploadID.toString(); const dimensions = { width, height }; const uri = getUploadURL(id, secret); if (type === 'photo') { return { id, type: 'photo', uri, dimensions }; } else if (loop) { // $FlowFixMe add thumbnailID, thumbnailURI once they're in DB return { id, type: 'video', uri, dimensions, loop }; } else { // $FlowFixMe add thumbnailID, thumbnailURI once they're in DB return { id, type: 'video', uri, dimensions }; } } async function fetchMedia( viewer: Viewer, mediaIDs: $ReadOnlyArray, ): Promise<$ReadOnlyArray> { const query = SQL` SELECT id AS uploadID, secret AS uploadSecret, type AS uploadType, extra AS uploadExtra FROM uploads WHERE id IN (${mediaIDs}) AND uploader = ${viewer.id} AND container IS NULL `; const [result] = await dbQuery(query); return result.map(mediaFromRow); } async function fetchUploadsForMessage( viewer: Viewer, mediaMessageContents: $ReadOnlyArray, ): Promise<$ReadOnlyArray> { - const uploadIDs = []; - for (const mediaContent of mediaMessageContents) { - uploadIDs.push(mediaContent.uploadID); - if (mediaContent.type === 'video') { - uploadIDs.push(mediaContent.thumbnailUploadID); - } - } - + const uploadIDs = getUploadIDsFromMediaMessageServerDBContents( + mediaMessageContents, + ); const query = SQL` SELECT id AS uploadID, secret AS uploadSecret, type AS uploadType, extra AS uploadExtra FROM uploads WHERE id IN (${uploadIDs}) AND uploader = ${viewer.id} AND container IS NULL `; const [uploads] = await dbQuery(query); return uploads; } async function fetchMediaFromMediaMessageContent( viewer: Viewer, mediaMessageContents: $ReadOnlyArray, ): Promise<$ReadOnlyArray> { const uploads = await fetchUploadsForMessage(viewer, mediaMessageContents); const uploadMap = _keyBy('uploadID')(uploads); const media: Media[] = []; for (const mediaMessageContent of mediaMessageContents) { const primaryUploadID = mediaMessageContent.uploadID; const primaryUpload = uploadMap[primaryUploadID]; const primaryUploadSecret = primaryUpload.uploadSecret; const primaryUploadURI = getUploadURL(primaryUploadID, primaryUploadSecret); const uploadExtra = JSON.parse(primaryUpload.uploadExtra); const { width, height, loop } = uploadExtra; const dimensions = { width, height }; if (mediaMessageContent.type === 'photo') { media.push({ type: 'photo', id: primaryUploadID, uri: primaryUploadURI, dimensions, }); continue; } const thumbnailUploadID = mediaMessageContent.thumbnailUploadID; const thumbnailUpload = uploadMap[thumbnailUploadID]; const thumbnailUploadSecret = thumbnailUpload.uploadSecret; const thumbnailUploadURI = getUploadURL( thumbnailUploadID, thumbnailUploadSecret, ); const video = { type: 'video', id: primaryUploadID, uri: primaryUploadURI, dimensions, thumbnailID: thumbnailUploadID, thumbnailURI: thumbnailUploadURI, }; media.push(loop ? { ...video, loop } : video); } return media; } export { fetchUpload, fetchUploadChunk, getUploadSize, getUploadURL, mediaFromRow, fetchMedia, fetchMediaFromMediaMessageContent, }; diff --git a/keyserver/src/updaters/upload-updaters.js b/keyserver/src/updaters/upload-updaters.js index 260d6b155..be3c516a2 100644 --- a/keyserver/src/updaters/upload-updaters.js +++ b/keyserver/src/updaters/upload-updaters.js @@ -1,41 +1,38 @@ // @flow import type { MediaMessageServerDBContent } from 'lib/types/messages/media.js'; +import { getUploadIDsFromMediaMessageServerDBContents } from 'lib/types/messages/media.js'; import { dbQuery, SQL } from '../database/database'; import type { Viewer } from '../session/viewer'; async function assignMedia( viewer: Viewer, mediaIDs: $ReadOnlyArray, containerID: string, ): Promise { const query = SQL` UPDATE uploads SET container = ${containerID} WHERE id IN (${mediaIDs}) AND uploader = ${viewer.id} AND container IS NULL `; await dbQuery(query); } async function assignMessageContainerToMedia( viewer: Viewer, mediaMessageContents: $ReadOnlyArray, containerID: string, ): Promise { - const mediaIDs: string[] = []; - for (const mediaContent of mediaMessageContents) { - mediaIDs.push(mediaContent.uploadID); - if (mediaContent.type === 'video') { - mediaIDs.push(mediaContent.thumbnailUploadID); - } - } + const uploadIDs = getUploadIDsFromMediaMessageServerDBContents( + mediaMessageContents, + ); const query = SQL` UPDATE uploads SET container = ${containerID} - WHERE id IN (${mediaIDs}) AND uploader = ${viewer.id} AND container IS NULL + WHERE id IN (${uploadIDs}) AND uploader = ${viewer.id} AND container IS NULL `; await dbQuery(query); } export { assignMedia, assignMessageContainerToMedia }; diff --git a/lib/types/messages/media.js b/lib/types/messages/media.js index f7ebb9e8e..5cd7daa31 100644 --- a/lib/types/messages/media.js +++ b/lib/types/messages/media.js @@ -1,39 +1,54 @@ // @flow import type { Media } from '../media-types'; import type { RelativeUserInfo } from '../user-types'; export type MediaMessageData = { type: 15, localID?: string, // for optimistic creations. included by new clients threadID: string, creatorID: string, time: number, media: $ReadOnlyArray, }; export type RawMediaMessageInfo = { ...MediaMessageData, id?: string, // null if local copy without ID yet }; export type MediaMessageInfo = { type: 15, id?: string, // null if local copy without ID yet localID?: string, // for optimistic creations threadID: string, creator: RelativeUserInfo, time: number, // millisecond timestamp media: $ReadOnlyArray, }; export type MediaMessageServerDBContent = | { +type: 'photo', +uploadID: string, } | { +type: 'video', +uploadID: string, +thumbnailUploadID: string, }; + +function getUploadIDsFromMediaMessageServerDBContents( + mediaMessageContents: $ReadOnlyArray, +): $ReadOnlyArray { + const uploadIDs: string[] = []; + for (const mediaContent of mediaMessageContents) { + uploadIDs.push(mediaContent.uploadID); + if (mediaContent.type === 'video') { + uploadIDs.push(mediaContent.thumbnailUploadID); + } + } + return uploadIDs; +} + +export { getUploadIDsFromMediaMessageServerDBContents };