2023-06-19 00:50:46 +02:00

179 lines
5.8 KiB
TypeScript

import { objectMap, objectOmit } from '@dzeio/object-util'
import URLManager from '@dzeio/url-manager'
import type { APIRoute } from 'astro'
import { evaluate } from 'mathjs'
import { exec as execSync } from 'node:child_process'
import fs from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path/posix'
import { promisify } from 'node:util'
import FilesUtils from '../../../libs/FilesUtils'
import { buildRFC7807 } from '../../../libs/RFCs/RFC7807'
import { getParams } from '../../../libs/gcodeUtils'
const exec = promisify(execSync)
let tmpDir: string
/**
* body is the stl
* query
* price: algorithm from settings
* adionnal settings from https://manual.slic3r.org/advanced/command-line
*/
export const post: APIRoute = async ({ request }) => {
if (!tmpDir) {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'paas-'))
}
const query = new URLManager(request.url).query()
const file = (Math.random() * 1000000).toFixed(0)
console.log('started processing new request', file)
await fs.mkdir(`${tmpDir}/files`, { recursive: true })
const overrides = objectOmit(query, 'algo', 'config')
const configName = query?.config ?? 'config'
let config = `${import.meta.env.CONFIGS_PATH}/` + configName + '.ini'
if (!await FilesUtils.exists(config)) {
console.log('request finished in error :(', file)
return buildRFC7807({
type: '/missing-config-file',
status: 404,
title: 'Configuration file is missing',
details: `the configuration file "${configName}" is not available on the remote server`
})
}
const stlPath = `${tmpDir}/files/${file}.stl`
const gcodePath = `${tmpDir}/files/${file}.gcode`
// write file
await fs.writeFile(stlPath, new Uint8Array(Buffer.from(await request.arrayBuffer())), {
encoding: null
})
// console.log(fs.statSync(stlPath).size, req.body.length)
let additionnalParams = objectMap(overrides, (value, key) => `--${(key as string).replace(/_/g, '-')} ${value}`).join(' ')
const slicer = import.meta.env.PRUSASLICER_PATH ?? 'prusa-slicer'
if (slicer.includes('prusa')) {
additionnalParams += ' --export-gcode'
}
const cmd = `${slicer} ${stlPath} --load ${config} --output ${gcodePath} ${additionnalParams}`
try {
await exec(cmd, {
timeout: 60000
})
} catch (e: any) {
console.log('request finished in error :(', file)
const line = e.toString()
console.error(e)
if (line.includes('Objects could not fit on the bed')) {
await fs.rm(stlPath)
return buildRFC7807({
type: '/object-too-large',
status: 413,
title: 'Object is too large'
})
} else if (line.includes('No such file')) {
await fs.rm(stlPath)
return buildRFC7807({
type: '/missing-config-file',
status: 404,
title: 'Configuration file is missing',
details: `the configuration file "${configName}" is not available on the remote server`
})
} else if (line.includes('Unknown option')) {
await fs.rm(stlPath)
return buildRFC7807({
type: '/slicer-option-unknown',
status: 400,
title: ' config override doew not exists',
details: 'an override does not exists, please contact an administrator or refer to the documentation'
})
} else if (
line.includes('is not recognized as an internal or external command') ||
line.includes('.dll was not loaded')
) {
await fs.rm(stlPath)
return buildRFC7807({
type: '/slicer-not-found',
status: 408,
title: 'the slicer used to process this file has not been found',
details: 'the server has a misconfiguration meaning that we can\'t process the request, please contact an administrator',
additionnalInfo: line.includes('dll') ? 'Missing DLL' : 'Slicer not found '
})
} else if (line.includes('ETIMEDOUT')) {
await fs.rm(stlPath)
return buildRFC7807({
type: '/timed-out-slicing',
status: 408,
title: 'Timed out slicing file',
detail: `The file you are trying to process takes too long to be processed`,
processingTimeoutMillis: 60000
})
}
return buildRFC7807({
type: '/general-input-output-error',
status: 500,
title: 'General I/O error',
details: 'A server error make it impossible to slice the file, please contact an administrator with the json error',
fileId: file,
config: configName,
// fileSize: req.body.length,
overrides: overrides,
serverMessage:
e.toString().replace(cmd, '***SLICER***').replace(stlPath, configName ?? `***FILE***`).replace(`${path}/configs/`, '').replace('.ini', '')
})
}
const gcode = await fs.readFile(gcodePath, 'utf-8')
await fs.rm(stlPath)
await fs.rm(gcodePath)
const params = getParams(gcode)
let price: string | undefined
if (query?.algo) {
let algo = decodeURI(query.algo as string)
// objectLoop(params, (value, key) => {
// if (typeof value !== 'number') {
// return
// }
// while (algo.includes(key)) {
// algo = algo.replace(key, value.toString())
// }
// })
try {
const tmp = evaluate(algo, params)
if (typeof tmp === 'number') {
price = tmp.toFixed(2)
} else {
return buildRFC7807({
type: '/algorithm-error',
status: 500,
title: 'Algorithm compilation error',
details: 'It seems the algorithm resolution failed',
algorithm: algo,
algorithmError: 'Algorithm return a Unit',
parameters: params
})
}
} catch (e) {
console.dir(e)
return buildRFC7807({
type: '/algorithm-error',
status: 500,
title: 'Algorithm compilation error',
details: 'It seems the algorithm resolution failed',
algorithm: algo,
algorithmError: e,
parameters: params
})
}
}
console.log('request successfull :)', file)
return {
status: 200,
body: JSON.stringify({
price: price ? parseFloat(price) : undefined,
...getParams(gcode),
gcode
})
}
}