Remove image actor service as not needed

This commit is contained in:
John Lyon-Smith
2018-05-11 15:34:47 -07:00
parent dadad6434f
commit 84babf0e4b
8 changed files with 54 additions and 394 deletions

View File

@@ -4,7 +4,6 @@
server: 'dar-test-server',
api: 'dar-test-api',
email: 'dar-test-email',
image: 'dar-test-image',
},
uri: {
mongo: 'mongodb://localhost/dar-test-v1',

View File

@@ -4,7 +4,6 @@
server: 'dar-server',
api: 'dar-api',
email: 'dar-email',
image: 'dar-image',
},
uri: {
mongo: 'mongodb://localhost/dar-v1',

View File

@@ -11,8 +11,6 @@
"test": "jest",
"actor:api": "monzilla 'src/api/**/*.js:src/database/**/*.js' -- babel-node src/api/index.js",
"actor:api:debug": "babel-node --inspect-brk src/api/index.js",
"actor:image": "monzilla 'src/image/**/*.js:src/(message-service|database)/**/*.js' -- babel-node src/image/index.js",
"actor:image:debug": "babel-node --inspect-brk src/image/index.js",
"actor:email": "monzilla 'src/email/**/*.js:src/(message-service|database)/**/*.js' -- babel-node src/email/index.js",
"actor:email:debug": "babel-node --inspect-brk src/email/index.js"
},

View File

@@ -1,18 +1,14 @@
import childProcess from 'child_process'
import path from 'path'
import timers from 'timers'
import autobind from 'autobind-decorator'
import childProcess from "child_process"
import path from "path"
import timers from "timers"
import autobind from "autobind-decorator"
@autobind
export class ServerTool {
constructor(toolName, log) {
this.toolName = toolName
this.log = log
this.actors = [
{ name: 'api' },
{ name: 'email' },
{ name: 'image' },
]
this.actors = [{ name: "api" }, { name: "email" }]
}
restart(actor) {
@@ -27,7 +23,11 @@ export class ServerTool {
const backOffTime = (Math.pow(2, Math.min(actor.backOff, 7)) - 1) * 1000
if (backOffTime > 0) {
this.log.warn(`Actor '${actor.name}' died quickly, waiting ${Math.floor(backOffTime / 1000)} seconds to restart`)
this.log.warn(
`Actor '${actor.name}' died quickly, waiting ${Math.floor(
backOffTime / 1000
)} seconds to restart`
)
} else {
this.log.warn(`Actor ${actor.name} died, restarting`)
}
@@ -36,9 +36,9 @@ export class ServerTool {
actor.starts += 1
actor.startTime = Date.now()
actor.proc = childProcess.fork(actor.modulePath)
actor.proc.on('exit', (code, signal) => {
actor.proc.on("exit", (code, signal) => {
// Don't restart if the exit was clean or Control+C
if (code === 0 || signal === 'SIGINT') {
if (code === 0 || signal === "SIGINT") {
this.log.info(`Actor ${actor.name} terminated normally`)
return
}
@@ -53,43 +53,53 @@ export class ServerTool {
let promises = []
this.actors.forEach((actor) => {
promises.push(new Promise((resolve, reject) => {
actor.modulePath = path.join(__dirname, actor.name)
actor.startTime = Date.now()
actor.backOff = 0
actor.timeToInit = actor.timeToInit || 3000
actor.proc = childProcess.fork(actor.modulePath)
actor.proc.on('exit', (code, signal) => {
const timeSinceStart = Date.now() - actor.startTime
promises.push(
new Promise((resolve, reject) => {
actor.modulePath = path.join(__dirname, actor.name)
actor.startTime = Date.now()
actor.backOff = 0
actor.timeToInit = actor.timeToInit || 3000
actor.proc = childProcess.fork(actor.modulePath)
actor.proc.on("exit", (code, signal) => {
const timeSinceStart = Date.now() - actor.startTime
if (timeSinceStart < actor.timeToInit) {
this.log.error(`Actor '${actor.name}' exited during initialization`)
if (timeSinceStart < actor.timeToInit) {
this.log.error(
`Actor '${actor.name}' exited during initialization`
)
if (this.actors) {
this.actors.forEach((otherActor) => {
if (otherActor !== actor) {
this.log.info(`Terminating actor ${actor.name}, pid ${otherActor.proc.pid}`)
otherActor.proc.kill('SIGINT')
otherActor.proc = null
}
})
if (this.actors) {
this.actors.forEach((otherActor) => {
if (otherActor !== actor) {
this.log.info(
`Terminating actor ${actor.name}, pid ${
otherActor.proc.pid
}`
)
otherActor.proc.kill("SIGINT")
otherActor.proc = null
}
})
actor.proc = null
this.actors = null
actor.proc = null
this.actors = null
}
return reject(
new Error("All actors must initialize on first start")
)
}
return reject(new Error('All actors must initialize on first start'))
}
this.restart(actor)
this.restart(actor)
})
timers.setTimeout(() => {
if (actor.proc) {
resolve()
}
}, actor.timeToInit)
this.log.info(`Started actor '${actor.name}', pid ${actor.proc.pid}`)
})
timers.setTimeout(() => {
if (actor.proc) {
resolve()
}
}, actor.timeToInit)
this.log.info(`Started actor '${actor.name}', pid ${actor.proc.pid}`)
})) // new Promise()
) // new Promise()
})
return Promise.all(promises)

View File

@@ -29,7 +29,7 @@ class SendMessageTool {
usage: ${this.toolName} [options] <file>
options:
-x --exchange <exchange> Exchange to send the message too, e.g. tmr-image
-x --exchange <exchange> Exchange to send the message too
-t --type <message-type> The type of the message content
`)
return 0

View File

@@ -22,7 +22,6 @@ export let userSchema = new Schema(
unique: true,
},
newEmail: { type: String, match: regExpPattern.email },
thumbnailImageId: { type: Schema.Types.ObjectId },
emailToken: {
type: {
value: String,
@@ -71,8 +70,6 @@ userSchema.methods.toClient = function(authUser) {
_id: this._id,
email: this.email,
emailValidated: !!this.emailToken !== true,
imageId: this.imageId,
thumbnailImageId: this.thumbnailImageId,
firstName: this.firstName,
lastName: this.lastName,
administrator: this.administrator,

View File

@@ -1,298 +0,0 @@
import Canvas from 'canvas'
import fs from 'fs'
import util from 'util'
import createError from 'http-errors'
import autobind from 'autobind-decorator'
import stream from 'stream'
function streamToBuffer(readable) {
return new Promise((resolve, reject) => {
var chunks = []
var writeable = new stream.Writable()
writeable._write = function (chunk, enc, done) {
chunks.push(chunk)
done()
}
readable.on('end', function () {
resolve(Buffer.concat(chunks))
})
readable.on('error', (err) => {
reject(err)
})
readable.pipe(writeable);
})
}
function pipeToGridFS(readable, gfsWriteable) {
const promise = new Promise((resolve, reject) => {
readable.on('error', (error) => {
reject(error)
})
gfsWriteable.on('error', (error) => {
reject(error)
})
gfsWriteable.on('close', (file) => {
resolve(file)
})
})
readable.pipe(gfsWriteable)
return promise
}
function loadImage(buf) {
return new Promise((resolve, reject) => {
const image = new Canvas.Image()
function cleanup () {
image.onload = null
image.onerror = null
}
image.onload = () => {
cleanup();
resolve(image)
}
image.onerror = () => {
cleanup();
reject(new Error(`Failed to load the image "${buf}"`))
}
image.src = buf
})
}
// Derived from https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images
function getExifOrientation(buf) {
if (buf.length < 2 || buf.readUInt16BE(0) != 0xFFD8) {
return -2
}
let length = buf.byteLength
let offset = 2
while (offset < length) {
let marker = buf.readUInt16BE(offset)
offset += 2
if (marker == 0xFFE1) {
if (buf.readUInt32BE(offset += 2) != 0x45786966) {
return -1
}
let little = (buf.readUInt16BE(offset += 6) == 0x4949)
offset += (little ? buf.readUInt32LE(offset + 4) : buf.readUInt32BE(offset + 4))
const numTags = (little ? buf.readUInt16LE(offset) : buf.readUInt16BE(offset))
offset += 2
for (let i = 0; i < numTags; i++) {
let val = (little ? buf.readUInt16LE(offset + (i * 12)) : buf.readUInt16BE(offset + (i * 12)))
if (val === 0x0112) {
return (little ? buf.readUInt16LE(offset + (i * 12) + 8) : buf.readUInt16BE(offset + (i * 12) + 8))
}
}
} else if ((marker & 0xFF00) != 0xFF00) {
break
} else {
offset += buf.readUInt16BE(offset)
}
}
}
function normalizeOrientation(image, orientation) {
let width = image.width
let height = image.height
let canvas = new Canvas(width, height)
let ctx = canvas.getContext("2d")
if (4 < orientation && orientation < 9) {
canvas.width = height
canvas.height = width
} else {
canvas.width = width
canvas.height = height
}
switch (orientation) {
case 2: ctx.transform(-1, 0, 0, 1, width, 0); break
case 3: ctx.transform(-1, 0, 0, -1, width, height ); break
case 4: ctx.transform(1, 0, 0, -1, 0, height ); break
case 5: ctx.transform(0, 1, 1, 0, 0, 0); break
case 6: ctx.transform(0, 1, -1, 0, height , 0); break
case 7: ctx.transform(0, -1, -1, 0, height , width); break
case 8: ctx.transform(0, -1, 1, 0, 0, width); break
default: return Promise.resolve(image)
}
ctx.drawImage(image, 0, 0)
return loadImage(canvas.toBuffer())
}
@autobind
export class ImageHandlers {
constructor(container) {
this.db = container.db
this.log = container.log
}
scaleImage(options) {
const {
newWidth = 400,
newHeight = 100,
scaleMode = 'scaleToFill',
inputFile,
inputAssetId,
outputFile
} = options
if (!inputFile && !inputAssetId) {
return Promise.reject(createError.BadRequest(`No inputAssetId or inputFile given`))
}
let canvas = new Canvas(newWidth, newHeight)
let ctx = canvas.getContext("2d")
ctx.imageSmoothingEnabled = true
let loadPromise = null
if (inputFile) {
loadPromise = util.promisify(fs.readFile)(inputFile)
} else {
loadPromise = streamToBuffer(this.db.gridfs.createReadStream({ _id: inputAssetId }))
}
let orientation
return loadPromise.then((buf) => {
orientation = getExifOrientation(buf)
return loadImage(buf)
}).then((img) => {
return normalizeOrientation(img, orientation)
}).then((img) => {
let x = 0
let y = 0
let scale = 1
switch (scaleMode) {
case 'aspectFill':
if (img.width - newWidth > img.height - newHeight) {
scale = newHeight / img.height
x = -(img.width * scale - newWidth) / 2
} else {
scale = newWidth / img.width
y = -(img.height * scale - newHeight) / 2
}
ctx.drawImage(img, x, y, img.width * scale, img.height * scale)
break
case 'aspectFit':
if (img.width - newWidth > img.height - newHeight) {
scale = newWidth / img.width
y = (newHeight - img.height * scale) / 2
} else {
scale = newHeight / img.height
x = (newWidth - img.width * scale) / 2
}
ctx.drawImage(img, x, y, img.width * scale, img.height * scale)
break
case 'scaleToFill':
default:
ctx.drawImage(img, 0, 0, newWidth, newHeight)
break
}
let savePromise = null
let readable = canvas.createPNGStream()
let writeable = null
if (outputFile) {
writeable = fs.createWriteStream(outputFile)
} else {
const _id = this.db.newObjectId()
writeable = this.db.gridfs.createWriteStream({
_id,
filename: _id + '.png',
content_type: 'image/png',
metadata: {
scaledFrom: this.db.newObjectId(inputAssetId),
width: newWidth,
height: newHeight
}
})
}
return pipeToGridFS(readable, writeable).then((file) => {
let res = {}
if (outputFile) {
res.outputFile = outputFile
} else if (file) {
res.outputAssetId = file._id
}
return Promise.resolve(res)
})
})
}
createPlaceholder(options) {
const {
width = 400,
height = 100,
fontSize = 36,
fontName = 'helvetica neue, arial black, sans serif',
fontWeight = 'bold',
background = '#1B1B1B',
foreground = '#333333',
outputFile
} = options
let canvas = new Canvas(width, height)
let ctx = canvas.getContext("2d")
const text = `${width}x${height}`
ctx.fillStyle = background
ctx.fillRect(0, 0, width, height)
ctx.font = `${fontWeight} ${fontSize}px ${fontName}`
ctx.fillStyle = foreground
const tm = ctx.measureText(text)
if (tm.width <= width / 2 && fontSize <= height / 2) {
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, width / 2, height / 2, width)
}
let promise = null
let readable = canvas.createPNGStream()
let writeable = null
if (outputFile) {
writeable = fs.createWriteStream(outputFile)
} else {
const _id = this.db.newObjectId()
writeable = this.db.gridfs.createWriteStream({
_id,
filename: `${width}x${height}.png`,
content_type: 'image/png',
metadata: {
width: width,
height: height,
}
})
}
return pipeToGridFS(readable, writeable).then((file) => {
let res = {}
if (outputFile) {
res.outputFile = outputFile
} else if (file) {
res.outputAssetId = file._id
}
return Promise.resolve(res)
})
}
}

View File

@@ -1,45 +0,0 @@
import config from "config"
import pino from "pino"
import * as pinoExpress from "pino-pretty-express"
import { DB } from "../database"
import { MS } from "../message-service"
import { ImageHandlers } from "./ImageHandlers"
import path from "path"
import fs from "fs"
const serviceName = config.get("serviceName.image")
const isProduction = process.env.NODE_ENV === "production"
let log = null
if (isProduction) {
log = pino(
{ name: serviceName },
fs.createWriteStream(path.join(config.get("logDir"), serviceName + ".log"))
)
} else {
const pretty = pinoExpress.pretty({})
pretty.pipe(process.stdout)
log = pino({ name: serviceName }, pretty)
}
const ms = new MS(serviceName, { durable: false }, log)
const db = new DB()
let container = { db, ms, log }
const mongoUri = config.get("uri.mongo")
const amqpUri = config.get("uri.amqp")
Promise.all([db.connect(mongoUri), ms.connect(amqpUri)])
.then(() => {
log.info(`Connected to MongoDB at ${mongoUri}`)
log.info(`Connected to RabbitMQ at ${amqpUri}`)
container = {
...container,
handlers: new ImageHandlers(container),
}
ms.listen(container.handlers)
})
.catch((err) => {
log.error(isProduction ? err.message : err)
})