UNPKG

@paperbits/core

Version:
168 lines (137 loc) 7.22 kB
import * as MediaUtils from "@paperbits/common/media/mediaUtils"; import parallel from "await-parallel-limit"; import { maxParallelPublisingTasks } from "@paperbits/common/constants"; import { HttpClient, HttpResponse } from "@paperbits/common/http"; import { IPublisher } from "@paperbits/common/publishing"; import { Query } from "@paperbits/common/persistence"; import { IBlobStorageStream } from "@paperbits/common/persistence/IBlobStorageStream"; import { IMediaService, MediaContract, MediaVariantContract } from "@paperbits/common/media"; import { Logger } from "@paperbits/common/logging"; import { normalizePermalink } from "@paperbits/common/permalinks/utils"; import { RegExps } from "@paperbits/common"; import { ReadStream } from "node:fs"; export class MediaPublisher implements IPublisher { constructor( private readonly mediaService: IMediaService, private readonly blobStorage: IBlobStorageStream, private readonly outputBlobStorage: IBlobStorageStream, private readonly httpClient: HttpClient, private readonly logger: Logger ) { } private async publishFromUrl(permalink: string, mediaFile: MediaVariantContract): Promise<void> { let response: HttpResponse<any>; try { response = await this.httpClient.send({ url: mediaFile.downloadUrl }); } catch (error) { this.logger.trackEvent("Publishing", { message: `Could not download media from URL ${mediaFile.downloadUrl}. ${error.message}` }); return null; } if (response?.statusCode !== 200) { this.logger.trackEvent("Publishing", { message: `Could not download media from URL ${mediaFile.downloadUrl}. Status code: ${response?.statusCode}` }); return null; } const content = response.toByteArray(); await this.uploadToStorage(permalink, content, mediaFile.mimeType); } private async publishFromStorage(permalink: string, mediaFile: MediaVariantContract): Promise<void> { let content: Uint8Array; try { content = await this.blobStorage.downloadBlob(mediaFile.blobKey); } catch (error) { this.logger.trackEvent("Publishing", { message: `Could not download media ${mediaFile.blobKey} from source storage. ${error.message}` }); return null; } if (!content) { this.logger.trackEvent("Publishing", { message: `Blob with key ${mediaFile.blobKey} not found in source storage.` }); return null; } await this.uploadToStorage(permalink, content, mediaFile.mimeType); } private async publishFromStorageStream(permalink: string, mediaFile: MediaVariantContract): Promise<void> { try { const contentStream = await this.blobStorage.getBlobAsStream(mediaFile.blobKey); if (!contentStream) { this.logger.trackEvent("Publishing", { message: `Blob with key ${mediaFile.blobKey} not found in source storage.` }); return null; } await this.outputBlobStorage.uploadStreamToBlob(permalink, <ReadStream>contentStream, mediaFile.mimeType); } catch (error) { this.logger.trackEvent("Publishing", { message: `Could not download media ${mediaFile.blobKey} from source storage. ${error.message}` }); return null; } } private async uploadToStorage(permalink: string, content: Uint8Array, mimeType: string): Promise<void> { try { await this.outputBlobStorage.uploadBlob(permalink, content, mimeType); } catch (error) { throw new Error(`Unable to upload media file to destination storage. ${error.stack || error.message}`); } } private async renderMediaFile(permalink: string, mediaFile: MediaVariantContract): Promise<void> { if (mediaFile.blobKey) { if (this.blobStorage.getBlobAsStream) { await this.publishFromStorageStream(permalink, mediaFile); } else { await this.publishFromStorage(permalink, mediaFile); } return; } if (mediaFile.downloadUrl) { await this.publishFromUrl(permalink, mediaFile); return; } } public async publish(): Promise<void> { const query: Query<MediaContract> = Query.from<MediaContract>(); let pagesOfResults = await this.mediaService.search(query); do { const tasks = []; const mediaContracts = pagesOfResults.value; for (const mediaContract of mediaContracts) { if (!mediaContract.permalink) { this.logger.trackEvent("Publishing", { message: `Skipping media with no permalink specified: "${mediaContract.fileName}".` }); continue; } const permalink = normalizePermalink(mediaContract.permalink); const isPermalinkValid = RegExps.permalink.test(permalink); if (!isPermalinkValid) { this.logger.trackEvent("Publishing", { message: `Skipping media "${mediaContract.fileName}" with invalid permalink: "${permalink}".` }); continue; } this.logger.trackEvent("Publishing", { message: `Publishing media ${mediaContract.fileName}...` }); const original: MediaVariantContract = { blobKey: mediaContract.blobKey, downloadUrl: mediaContract.downloadUrl, mimeType: mediaContract.mimeType }; if (!mediaContract.blobKey && !mediaContract.downloadUrl) { this.logger.trackEvent("Publishing", { message: `Skipping media with no blob key or download URL specified: ${mediaContract.fileName}.` }); continue; } tasks.push(() => this.renderMediaFile(permalink, original)); if (mediaContract.variants) { for (const variant of mediaContract.variants) { if (!mediaContract.blobKey && !mediaContract.downloadUrl) { this.logger.trackEvent("Publishing", { message: `Skipping media variant with no blob key or download URL specified: ${mediaContract.fileName}.` }); continue; } const variantPermalink = MediaUtils.getPermalinkForMediaVariant(permalink, variant); tasks.push(() => this.renderMediaFile(variantPermalink, variant)); } } } await parallel(tasks, maxParallelPublisingTasks); if (pagesOfResults.takeNext) { pagesOfResults = await pagesOfResults.takeNext(); } else { pagesOfResults = null; } } while (pagesOfResults); } }