// host-cache.js // Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; const dgram = require('dgram'); const uuidv4 = require('uuid').v4; const { SiteService, SiteError } = require('../../lib/site-lib'); class HostCacheService extends SiteService { constructor (dtp) { super(dtp, module.exports); this.transactions = { }; } async start ( ) { await super.start(); this.log.info('creating UDP host-cache socket'); this.hostCache = dgram.createSocket('udp4', this.onMessage.bind(this)); this.hostCache.on('error', this.onError.bind(this)); this.log.info('connecting UDP host-cache socket'); this.hostCache.bind(0, '127.0.0.1'); const HOST_PORT = parseInt(process.env.DTP_HOST_CACHE_PORT || '8000', 10); this.hostCache.connect(HOST_PORT, '127.0.0.1'); } async stop ( ) { if (this.hostCache) { this.log.info('disconnecting UDP host-cache socket'); this.hostCache.disconnect(); delete this.hostCache; } await super.stop(); } async getFile (bucket, key) { return new Promise((resolve, reject) => { const transaction = { tid: uuidv4(), bucket, key, resolve, reject }; this.transactions[transaction.tid] = transaction; const message = JSON.stringify({ tid: transaction.tid, cmd: 'getFile', params: { bucket, key }, }); this.hostCache.send(message); }); } async onMessage (message, rinfo) { message = message.toString('utf8'); message = JSON.parse(message); switch (message.res.cmd) { case 'getFile': return this.onGetFile(message, rinfo); } } async onGetFile (message) { const transaction = this.transactions[message.tid]; if (!transaction) { this.log.error('getFile response received with no matching transaction', { tid: message.tid }); return; } if (!message.res.success) { transaction.reject(new SiteError(message.res.statusCode, message.res.message)); delete this.transactions[message.tid]; return; } transaction.resolve({ success: message.res.success, message: message.res.message, file: message.res.file, flags: message.flags, duration: message.duration, }); delete this.transactions[message.tid]; } async onError (error) { this.log.error('onError', { error }); if ((error.errno !== -111) || (error.code !== 'ECONNREFUSED')) { return; } const keys = Object.keys(this.transactions); keys.forEach((key) => { const transaction = this.transactions[key]; transaction.reject(error); delete this.transactions[key]; }); } } module.exports = { logId: 'svc:host-cache', index: 'hostCache', className: 'HostCacheService', create: (dtp) => { return new HostCacheService(dtp); }, };