UNPKG

@ngageoint/mage.image.service

Version:

Orient images attached to MAGE observations according to EXIF meta-data and generate configurable size thumbnails.

89 lines (85 loc) 4.43 kB
import mongoose from 'mongoose' import { GetDbConnection } from '@ngageoint/mage.service/lib/plugins.api/plugins.api.db' import { EventProcessingState, FindUnprocessedImageAttachments, UnprocessedAttachmentReference } from './processor' import { ObjectId } from 'mongodb' export function FindUnprocessedAttachments(getDbConn: GetDbConnection, console: Console): FindUnprocessedImageAttachments { return async (eventProcessingStates: EventProcessingState[], lastModifiedAfter: number | null, lastModifiedBefore: number | null, limit: number | null): Promise<AsyncIterable<UnprocessedAttachmentReference>> => { return { [Symbol.asyncIterator]: async function * (): AsyncGenerator<UnprocessedAttachmentReference> { limit = Number(limit) let remainingCount = limit > 0 ? limit : Number.POSITIVE_INFINITY const conn = await getDbConn() const eventStateCursor = eventStatesWithCollectionNames(conn, eventProcessingStates, console) for await (const eventState of eventStateCursor) { const eventId = eventState.event.id const queryStages = createAggregationPipeline(eventState, lastModifiedAfter, lastModifiedBefore) if (limit) { queryStages.push({ $limit: remainingCount }) } console.info(`query unprocessed attachments from event ${eventId} (${eventState.event.name}) newer than ${new Date(eventState.latestAttachmentProcessedTimestamp).toISOString()}`) const collection = conn.collection(eventState.collectionName) const cursor = await collection.aggregate<UnprocessedAttachmentReferenceDocument>(queryStages) for await (const doc of cursor) { yield { eventId: eventState.event.id, observationId: doc.observationId.toString(), attachmentId: doc.attachmentId.toString() } if (--remainingCount === 0) { console.info(`reached unprocessed attachment limit ${limit}; cancelling query iteration`) return await cursor.close().then(_ => eventStateCursor.return!(null)).then(_ => null) } } } } } } } type UnprocessedAttachmentReferenceDocument = Record<keyof UnprocessedAttachmentReference, ObjectId> async function * eventStatesWithCollectionNames(conn: mongoose.Connection, eventStates: Iterable<EventProcessingState>, console: Console): AsyncIterableIterator<EventProcessingState & { collectionName: string }> { for (const eventState of eventStates) { const found = await conn.collection('events').findOne<{ collectionName: string } | null>({ _id: eventState.event.id }) if (found) { yield { ...eventState, collectionName: found.collectionName } } else { console.warn(`event not found for event processing state:`, eventState) } } } function createAggregationPipeline(eventState: EventProcessingState, lastModifiedAfter: number | null, lastModifiedBefore: number | null): object[] { const match: any = { $match: { 'attachments.oriented': false, 'attachments.contentType': /^image/, $or: [ { 'states.0.name': { $eq: 'active' }}, { 'states.0.name': { $exists: false }} ] }} lastModifiedAfter = typeof lastModifiedAfter === 'number' ? lastModifiedAfter : eventState.latestAttachmentProcessedTimestamp const lastModified = { $gte: new Date(lastModifiedAfter) } as any if (lastModifiedBefore) { lastModified.$lte = new Date(lastModifiedBefore) } match.$match = { lastModified, ...match.$match } /* TODO: how does mongo driver account for nondeterministic key order in this sort document? BSON keys are ordered, but JSON key order in Node/V8 is not guaranteed. */ const sort = { $sort: { lastModified: 1, _id: 1 }} const projectFilteredAttachments = { $project: { observationId: '$_id', _id: false, attachments: { $filter: { input: '$attachments', as: 'att', cond: { $and: [ { $eq: [ '$$att.oriented', false ] }, { $gt: [ '$$att.relativePath', null ] }, { $eq: [ { $indexOfBytes: [ '$$att.contentType', 'image/' ] }, 0 ] } ] } } } } } const unwindAttachments = { $unwind: '$attachments' } const projectAttachmentId = { $project: { observationId: 1, attachmentId: '$attachments._id' }} return [ match, sort, projectFilteredAttachments, unwindAttachments, projectAttachmentId ] }