bigbluebutton-Github/bigbluebutton-html5/imports/api/annotations/server/modifiers/addAnnotation.js

453 lines
12 KiB
JavaScript
Raw Normal View History

2016-11-19 01:35:28 +08:00
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import Annotations from '/imports/api/annotations';
import RedisPubSub from '/imports/startup/server/redis';
2017-08-31 07:12:03 +08:00
const ANNOTATION_TYPE_TEXT = 'text';
const ANNOTATION_TYPE_PENCIL = 'pencil';
2016-11-19 01:35:28 +08:00
// line, triangle, ellipse, rectangle
function handleCommonAnnotation(meetingId, whiteboardId, userId, annotation) {
2018-01-08 08:25:56 +08:00
const {
id, status, annotationType, annotationInfo, wbId, position,
} = annotation;
2016-11-19 01:35:28 +08:00
const selector = {
meetingId,
id,
userId,
2016-11-19 01:35:28 +08:00
};
2017-06-03 03:25:02 +08:00
const modifier = {
2016-11-19 01:35:28 +08:00
$set: {
whiteboardId,
2016-11-19 01:35:28 +08:00
meetingId,
id,
status,
annotationType,
annotationInfo,
wbId,
position,
},
$inc: { version: 1 },
};
2018-04-25 23:15:36 +08:00
// returning one document ready for upsert
2018-04-24 21:59:13 +08:00
return {
updateOne: {
'filter': selector,
'update': modifier,
'upsert': true
}
};
}
function handleTextUpdate(meetingId, whiteboardId, userId, annotation) {
2018-01-08 08:25:56 +08:00
const {
id, status, annotationType, annotationInfo, wbId, position,
} = annotation;
const selector = {
meetingId,
id,
userId,
};
annotationInfo.text = annotationInfo.text.replace(/[\r]/g, '\n');
const modifier = {
$set: {
2016-11-19 01:35:28 +08:00
whiteboardId,
meetingId,
id,
status,
annotationType,
annotationInfo,
wbId,
position,
2016-11-19 01:35:28 +08:00
},
$inc: { version: 1 },
2016-11-19 01:35:28 +08:00
};
2018-04-25 23:15:36 +08:00
// returning one document ready for upsert
2018-04-25 02:17:55 +08:00
return {
updateOne: {
'filter': selector,
'update': modifier,
'upsert': true
}
};
}
function handlePencilUpdate(meetingId, whiteboardId, userId, annotation) {
// fetching annotation statuses from the config
const ANOTATION_STATUSES = Meteor.settings.public.whiteboard.annotations.status;
const DRAW_START = ANOTATION_STATUSES.start;
const DRAW_UPDATE = ANOTATION_STATUSES.update;
const DRAW_END = ANOTATION_STATUSES.end;
const SERVER_CONFIG = Meteor.settings.private.app;
const PENCIL_CHUNK_SIZE = SERVER_CONFIG.pencilChunkLength || 100;
2018-01-08 08:25:56 +08:00
const {
id, status, annotationType, annotationInfo, wbId, position,
} = annotation;
const baseSelector = {
meetingId,
id,
userId,
whiteboardId,
};
let baseModifier;
let chunkSelector;
let chunkModifier;
// fetching the Annotation object
2018-04-25 23:15:36 +08:00
// if there's an update waiting inside a bulk, then use that info
// if not, then access Mongo
2018-04-24 21:59:13 +08:00
const bulkAnnotation = RedisPubSub.findAnnotationInsideBulk(baseSelector);
2018-04-25 23:15:36 +08:00
let Annotation = bulkAnnotation ? bulkAnnotation : Annotations.findOne(baseSelector);
// a helper func, to split the initial annotation.points into subdocuments
// returns an array of { selector, modifier } objects for subdocuments.
const createPencilObjects = () => {
const chunks = [];
// if the length of the points < PENCIL_CHUNK_SIZE then we simply return an array with one chunk
if (annotationInfo.points.length < PENCIL_CHUNK_SIZE) {
const chunkId = `${id}--${1}`;
chunks.push({
selector: {
meetingId,
userId,
id: chunkId,
},
modifier: {
$set: {
whiteboardId,
meetingId,
id: chunkId,
status,
annotationType,
annotationInfo,
wbId,
position,
},
$inc: { version: 1 },
},
2016-11-19 01:35:28 +08:00
});
return chunks;
}
2016-11-19 01:35:28 +08:00
// *default flow*
// length of the points >= PENCIL_CHUNK_SIZE, so we split them into subdocuments
2016-11-19 01:35:28 +08:00
// counter is used for generating ids.
let i = 0;
let counter = 1;
for (; i <= annotationInfo.points.length; i += PENCIL_CHUNK_SIZE, counter += 1) {
const chunkId = `${id}--${counter}`;
// we always need to attach the last coordinate from the previous subdocument
// to the front of the current subdocument, to connect the pencil path
const _annotationInfo = annotationInfo;
_annotationInfo.points = annotationInfo.points.slice(i === 0 ? 0 : i - 2, PENCIL_CHUNK_SIZE);
chunks.push({
selector: {
meetingId,
userId,
id: chunkId,
},
modifier: {
$set: {
whiteboardId,
meetingId,
id: chunkId,
status,
annotationType,
annotationInfo: _annotationInfo,
wbId,
position,
},
$inc: { version: 1 },
},
2016-11-19 01:35:28 +08:00
});
}
return chunks;
};
2018-04-24 21:59:13 +08:00
let operations = [];
switch (status) {
case DRAW_START: {
2018-04-24 21:59:13 +08:00
// on start we split the points
const chunks = createPencilObjects();
// create the 'pencil_base'
baseModifier = {
id,
userId,
meetingId,
whiteboardId,
position,
annotationType: 'pencil_base',
numberOfChunks: chunks.length,
2018-04-24 21:59:13 +08:00
lastChunkLength: chunks[chunks.length - 1].length ? chunks[chunks.length - 1].length : null,
lastCoordinate: [
annotationInfo.points[annotationInfo.points.length - 2],
annotationInfo.points[annotationInfo.points.length - 1],
],
};
2018-04-25 23:15:36 +08:00
// pushing all the chunks to the array of operations to add to the bulk later
for (let i = 0; i < chunks.length; i += 1) {
2018-04-24 21:59:13 +08:00
operations.push({
updateOne: {
'filter': chunks[i].selector,
'update': chunks[i].modifier,
'upsert': true
}
});
}
2018-04-25 23:15:36 +08:00
// pencil_base upsert to be added to the bulk
2018-04-24 21:59:13 +08:00
operations.push({
updateOne: {
'filter': baseSelector,
'update': baseModifier,
'upsert': true
}
});
2018-04-24 21:59:13 +08:00
return operations;
}
case DRAW_UPDATE: {
2018-04-24 21:59:13 +08:00
// checking if "pencil_base" exists
if (Annotation) {
2018-04-24 21:59:13 +08:00
const { numberOfChunks, lastChunkLength } = Annotation;
// if lastChunkLength < PENCIL_CHUNK_SIZE then we can simply push points to the last object
if (lastChunkLength < PENCIL_CHUNK_SIZE) {
// creating a modifier for 'pencil_base'
baseModifier = {
$set: {
lastChunkLength: lastChunkLength + annotation.annotationInfo.points.length,
lastCoordinate: [
annotationInfo.points[annotationInfo.points.length - 2],
annotationInfo.points[annotationInfo.points.length - 1],
],
},
};
const chunkId = `${id}--${numberOfChunks}`;
chunkSelector = {
meetingId,
userId,
id: chunkId,
};
// fetching the last pencil sub-document
2018-04-25 23:15:36 +08:00
// if there's a proper chunk update in the bulk, use its info
// if not, access Mongo
2018-04-24 21:59:13 +08:00
const bulkChunk = RedisPubSub.findChunkInsideBulk(chunkSelector);
let chunk = (bulkChunk && bulkChunk.$set) ? bulkChunk.$set : Annotations.findOne(chunkSelector);
2018-04-25 23:15:36 +08:00
// adding the coordinates to the end of the last sub-document
annotationInfo.points = chunk.annotationInfo.points.concat(annotationInfo.points);
chunkModifier = {
$set: {
annotationInfo,
},
$inc: { version: 1 },
};
// if lastChunkLength > PENCIL_CHUNK_SIZE then we need to create another chunk
} else if (lastChunkLength >= PENCIL_CHUNK_SIZE) {
baseModifier = {
$set: {
numberOfChunks: numberOfChunks + 1,
lastChunkLength: annotationInfo.points.length,
lastCoordinate: [
annotationInfo.points[annotationInfo.points.length - 2],
annotationInfo.points[annotationInfo.points.length - 1],
],
},
};
const chunkId = `${id}--${numberOfChunks + 1}`;
chunkSelector = {
meetingId,
userId,
id: chunkId,
};
// pushing the last coordinate to the front of the current chunk's points
annotationInfo.points.unshift(Annotation.lastCoordinate[0], Annotation.lastCoordinate[1]);
chunkModifier = {
$set: {
whiteboardId,
meetingId,
userId,
id: chunkId,
status,
annotationType,
annotationInfo,
wbId,
position: Annotation.position,
},
$inc: { version: 1 },
};
}
2018-04-25 23:15:36 +08:00
// chunk upsert to be added to the bulk later
2018-04-24 21:59:13 +08:00
operations.push({
updateOne: {
'filter': chunkSelector,
'update': chunkModifier,
'upsert': true
}
});
2018-04-25 23:15:36 +08:00
// pencil_base upsert to be added to the bulk later
2018-04-24 21:59:13 +08:00
operations.push({
updateOne: {
'filter': baseSelector,
'update': baseModifier,
'upsert': true
}
});
return operations;
}
// **default flow**
// if we are here then it means that Annotation object is not in the db
// So creating everything similar to DRAW_START case
const _chunks = createPencilObjects();
// creating 'pencil_base' based on the info we received from createPencilObjects()
baseModifier = {
id,
userId,
meetingId,
whiteboardId,
position,
annotationType: 'pencil_base',
numberOfChunks: _chunks.length,
lastChunkLength: _chunks[_chunks.length - 1].length,
lastCoordinate: [
annotationInfo.points[annotationInfo.points.length - 2],
annotationInfo.points[annotationInfo.points.length - 1],
],
};
2018-04-25 23:15:36 +08:00
// adding all the chunk upserts to the array of operations
for (let i = 0; i < _chunks.length; i += 1) {
2018-04-24 21:59:13 +08:00
operations.push({
updateOne: {
'filter': _chunks[i].selector,
'update': _chunks[i].modifier,
'upsert': true
}
});
}
2018-04-25 23:15:36 +08:00
// pencil_base upsert
2018-04-24 21:59:13 +08:00
operations.push({
updateOne: {
'filter': baseSelector,
'update': baseModifier,
'upsert': true
}
});
return operations;
}
case DRAW_END: {
2018-04-24 21:59:13 +08:00
// If a user just finished drawing with the pencil
// Removing all the sub-documents and replacing the 'pencil_base'
if (Annotation && Annotation.annotationType === 'pencil_base') {
// delete everything and replace base
const chunkIds = [];
for (let i = 0; i <= Annotation.numberOfChunks; i += 1) {
chunkIds.push(`${Annotation.id}--${i}`);
}
chunkSelector = {
meetingId,
userId,
id: { $in: chunkIds },
};
2018-04-24 21:59:13 +08:00
operations.push({
deleteMany: {
'filter': chunkSelector
}
});
}
// Updating the main pencil object with the final info
baseModifier = {
$set: {
whiteboardId,
meetingId,
id,
status,
annotationType,
annotationInfo,
wbId,
position,
},
$inc: { version: 1 },
$unset: {
numberOfChunks: '',
lastChunkLength: '',
lastCoordinate: '',
},
};
2018-04-24 21:59:13 +08:00
operations.push({
updateOne: {
'filter': baseSelector,
'update': baseModifier,
'upsert': true
}
});
return operations;
}
default: {
return {};
}
}
}
export default function addAnnotation(meetingId, whiteboardId, userId, annotation) {
check(meetingId, String);
check(whiteboardId, String);
check(annotation, Object);
let query;
switch (annotation.annotationType) {
case ANNOTATION_TYPE_TEXT:
query = handleTextUpdate(meetingId, whiteboardId, userId, annotation);
break;
case ANNOTATION_TYPE_PENCIL:
query = handlePencilUpdate(meetingId, whiteboardId, userId, annotation);
break;
default:
query = handleCommonAnnotation(meetingId, whiteboardId, userId, annotation);
2016-11-19 01:35:28 +08:00
break;
}
2018-04-25 23:15:36 +08:00
if(query instanceof Array) { // array of operations
2018-04-24 21:59:13 +08:00
RedisPubSub.addOperationsToBulk(query);
2018-04-25 23:15:36 +08:00
} else { // one operation
2018-04-24 21:59:13 +08:00
RedisPubSub.addToAnnotationsBulk(query);
}
2018-04-24 21:59:13 +08:00
2017-06-03 03:25:02 +08:00
}
2018-04-24 21:59:13 +08:00