import decompress from "decompress"; import sharp from 'sharp'; sharp.cache({ files : 0 }); import util from 'node:util'; import { execFile as npExecFile } from 'child_process'; const execFile = util.promisify(npExecFile); import fs from 'fs'; import path from 'path'; const collection = 'assets'; /** * Game objects manager, контролен клас за управление на игрови обекти */ class GameObjectsManager{ name = 'gameObject'; /** * Plugin initializer, инициализация на плъгин * @param {App} app The Application, обект приложение */ init(app){ const {db, config} = app; /** * Creates a game object, създаване на игрови обект * @param {Context} ctx Request context, контекст на заявката * @param {GameObject} data Asset data, данни за игровия обект */ this.create = async function(ctx, data){ data.id = (await db.getLastId(collection)) + 1; await db.create(collection, data); if (ctx.files?.file){ await this.addFile(data, ctx.files.file) await fs.promises.unlink(ctx.files.file.path) await db.update(collection, {id: data.id}, data); } return data; } /** * Retrieves game object from database, прочитане на обект от базата от данни * @param {Number} id Game object ID, идентификатор на обекта * @returns {GameObject} The game object, игрови обект */ this.read = async function(id){ id = parseInt(id); return await db.get(collection, {id}); } /** * Updates game object into the database, обновяване на игрови обект * @param {Context} ctx Request context, контекст на заявката * @param {GameObject} data Game object, данни за игровия обект */ this.update = async function(ctx, data){ data.id = parseInt(data.id); let object = await this.read(data.id); data = Object.assign(object, data); if (ctx.files?.file){ await this.addFile(data, ctx.files.file) await fs.promises.unlink(ctx.files.file.path) }if (ctx.files?.thumb){ await this.addThumb(data, ctx.files.thumb.path); await fs.promises.unlink(ctx.files.thumb.path) } await db.update(collection, {id: data.id}, data); return data; } /** * Removes game object from database, изтриване на игрови обект от базата от данни * @param {Number} id Game object ID, идентификатор на игровия обект */ this.remove = async function(id){ id = parseInt(id); await db.remove(collection, {id}); } /** * Assigns a file to a game object, закачване на файл към игрови обект * @param {GameObject} object Game object, игрови обект * @param {File} tmpFile A file, файл */ this.addFile = async function(object, tmpFile){ let i = tmpFile; let ofn = i.name; let ext = path.extname(ofn).toLowerCase(); let src = `${config.fs.repo}/source/${object.id}${ext}`; let def = `${config.fs.repo}/default/${object.id}`; await fs.promises.copyFile(i.path, src); object.asset = { ofn, name: `${object.id}${ext}` } if (ext == '.zip'){ let result = await decompress(src, def); object.asset.list = result.map(f=>f.path); object.asset.type = 'bundle'; object.asset.name = `${object.id}/` + result.find(f=>f.path.endsWith('.gltf'))?.path; }else{ object.asset.type = 'single'; await fs.promises.copyFile(src, def + ext); if (['.jpg', '.png', '.webp', '.mp4', '.avi', '.webv'].includes(ext)){ await this.addThumb(object, src); } } } /** * Assigns a thumbnail to a game object, закачане на представително изображение към игрови обект * @param {GameObject} object Game object, игрови обект * @param {File} thumbSrc A thumbnail, представително изображение */ this.addThumb = async function(object, thumbSrc){ let ext = path.extname(thumbSrc).toLowerCase(); //console.log(object, thumbSrc, ext); let dest = `${config.fs.repo}/thumb/${object.id}.webp`; if (['.jpg', '.png', '.webp'].includes(ext) || !ext){ await sharp(thumbSrc).resize({height: 250}).toFile(dest); }else if (['.mp4', '.avi', '.webv'].includes(ext)){ let frame = 1; await execFile('ffmpeg', [ '-i', thumbSrc, '-vf', `select=eq(n\\,${frame}),scale=-2:300`, '-vframes', 1, '-f', 'image2', '-y', dest]); } object.asset.thumb = `${object.id}.webp`; } /** * Returns list of GameObjects, търсене в игровите обекти по зададени критерии * @param {Object} query Query to DB, критерии - заявка към базата от данни * @returns {GameObject[]} Array of game objects, масив от игрови обекти */ this.list = async function(query = {}){ return await db.list(collection, { query, project: { name:1, id:1, type:1, asset:1} }); } } /** * Class starter, стартиране на класа * @param {App} app The application, базова апликация */ async start(app){ } } /** * GameObject entity, can be: panorama picture, 3d environment, 3d object, 2d object (picture), a player (3d), audio or video asset * Игрови обект, може да бъде панорамна снимка, триизмерна среда, обект или играч, двуизмерен обект, аудио или видео актив */ class GameObject { /** * Game object name, име на игровия обект * @type string */ name = null; /** * Game object type, тип на игровия обект * @type string */ type = null; /** * Associated file, асоцииран файл * @type File */ file = null; } export { GameObjectsManager }