Basic OBJ import (meshes, materials, vertex colors) (#713)
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
|
||||
import json
|
||||
from specklepy.objects import Base
|
||||
from specklepy.objects.other import RenderMaterial
|
||||
from specklepy.objects.geometry import Mesh, Point, Line
|
||||
from specklepy.transports.server import ServerTransport
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.api.credentials import get_default_account
|
||||
from specklepy.api import operations
|
||||
|
||||
import sys, os
|
||||
|
||||
from obj_file import ObjFile
|
||||
|
||||
TMP_RESULTS_PATH = '/tmp/import_result.json'
|
||||
DEFAULT_BRANCH = 'uploads'
|
||||
|
||||
def convert_material(obj_mat):
|
||||
speckle_mat = RenderMaterial()
|
||||
speckle_mat.name = obj_mat['name']
|
||||
if 'diffuse' in obj_mat:
|
||||
argb = [1,] + obj_mat['diffuse']
|
||||
speckle_mat.diffuse = int.from_bytes([int(val * 255) for val in argb], byteorder="big", signed=True)
|
||||
if 'dissolved' in obj_mat:
|
||||
speckle_mat.opacity = obj_mat['dissolved']
|
||||
return speckle_mat
|
||||
|
||||
def import_obj():
|
||||
file_path, user_id, stream_id, branch_name, commit_message = sys.argv[1:]
|
||||
print(f'ImportOBJ argv[1:]: {sys.argv[1:]}')
|
||||
|
||||
# Parse input
|
||||
obj = ObjFile(file_path)
|
||||
print(f'Parsed obj with {len(obj.faces)} faces ({len(obj.vertices) * 3} vertices)')
|
||||
|
||||
speckle_root = Base()
|
||||
speckle_root['@objects'] = []
|
||||
|
||||
for objname in obj.objects:
|
||||
print(f' Converting {objname}...')
|
||||
|
||||
speckle_obj = Base()
|
||||
speckle_obj.name = objname
|
||||
speckle_obj['@displayValue'] = []
|
||||
speckle_root['@objects'].append(speckle_obj)
|
||||
|
||||
for obj_mesh in obj.objects[objname]:
|
||||
speckle_vertices = [coord for point in obj_mesh['vertices'] for coord in point]
|
||||
speckle_faces = []
|
||||
for obj_face in obj_mesh['faces']:
|
||||
if len(obj_face) == 3:
|
||||
speckle_faces.append(0)
|
||||
elif len(obj_face) == 4:
|
||||
speckle_faces.append(1)
|
||||
else:
|
||||
speckle_faces.append(len(obj_face))
|
||||
speckle_faces.extend(obj_face)
|
||||
|
||||
has_vertex_colors = False
|
||||
for vc in obj_mesh['vertex_colors']:
|
||||
if vc is not None:
|
||||
has_vertex_colors = True
|
||||
colors = []
|
||||
if has_vertex_colors:
|
||||
for vc in obj_mesh['vertex_colors']:
|
||||
if vc is None:
|
||||
r, g, b = (1.0, 1.0, 1.0)
|
||||
else:
|
||||
r, g, b = vc
|
||||
argb = (1.0, r, g, b)
|
||||
color = int.from_bytes([int(val * 255) for val in argb], byteorder="big", signed=True)
|
||||
colors.append(color)
|
||||
|
||||
speckle_mesh = Mesh(
|
||||
vertices=speckle_vertices,
|
||||
faces=speckle_faces,
|
||||
colors=colors,
|
||||
textureCoordinates=[]
|
||||
)
|
||||
|
||||
obj_material = obj_mesh['material']
|
||||
if obj_material:
|
||||
speckle_mesh['renderMaterial'] = convert_material(obj_material)
|
||||
|
||||
speckle_obj['@displayValue'].append(speckle_mesh)
|
||||
|
||||
# Commit
|
||||
|
||||
client = SpeckleClient(host=os.getenv('SPECKLE_SERVER_URL', 'localhost:3000'), use_ssl=False)
|
||||
client.authenticate(os.environ['USER_TOKEN'])
|
||||
|
||||
if not client.branch.get(stream_id, branch_name):
|
||||
client.branch.create(stream_id, branch_name, 'File upload branch' if branch_name == 'uploads' else '')
|
||||
|
||||
transport = ServerTransport(client=client, stream_id=stream_id)
|
||||
id = operations.send(base=speckle_root, transports=[transport])
|
||||
|
||||
commit_id = client.commit.create(
|
||||
stream_id=stream_id,
|
||||
object_id=id,
|
||||
branch_name=(branch_name or DEFAULT_BRANCH),
|
||||
message=(commit_message or 'OBJ file upload'),
|
||||
source_application='OBJ'
|
||||
)
|
||||
|
||||
return commit_id
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
commit_id = import_obj()
|
||||
if not commit_id:
|
||||
raise Exception("Can't create commit")
|
||||
results = {'success': True, 'commitId': commit_id}
|
||||
except Exception as ex:
|
||||
print('ERROR: ' + str(ex))
|
||||
results = {'success': False, 'error': str(ex)}
|
||||
|
||||
with open(TMP_RESULTS_PATH, 'w') as f:
|
||||
json.dump(results, f)
|
||||
@@ -0,0 +1,76 @@
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class MtlFileCollection(object):
|
||||
def __init__(self, base_dir):
|
||||
self.base_dir = base_dir
|
||||
self.logged_unsupported = set()
|
||||
self.materials = {}
|
||||
self.crt_mat = None
|
||||
|
||||
def ensure_mat(self, directive):
|
||||
if self.crt_mat is None and f'no_mat_{directive}' not in self.logged_unsupported:
|
||||
print(f'Directive found outside material definition: {directive}')
|
||||
self.logged_unsupported.add(f'no_mat_{directive}')
|
||||
return self.crt_mat is not None
|
||||
|
||||
def mtllib(self, fpath):
|
||||
fpath = os.path.join(self.base_dir, os.path.basename(fpath))
|
||||
if not os.path.isfile(fpath):
|
||||
print(f'Missing MTL file: {fpath}')
|
||||
return
|
||||
|
||||
with open(fpath, 'r') as f:
|
||||
while True:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
if not line.strip() or line.startswith('#'):
|
||||
continue
|
||||
parts = line.strip().split(' ')
|
||||
if parts[0] == 'newmtl':
|
||||
mat_name = ' '.join(parts[1:])
|
||||
self.crt_mat = {'name': mat_name}
|
||||
self.materials[mat_name] = self.crt_mat
|
||||
elif parts[0] == 'Ka':
|
||||
if self.ensure_mat('Ka'):
|
||||
self.crt_mat['ambient'] = [float(x) for x in parts[1:]]
|
||||
elif parts[0] == 'Kd':
|
||||
if self.ensure_mat('Kd'):
|
||||
self.crt_mat['diffuse'] = [float(x) for x in parts[1:]]
|
||||
elif parts[0] == 'Ks':
|
||||
if self.ensure_mat('Ks'):
|
||||
self.crt_mat['specular_color'] = [float(x) for x in parts[1:]]
|
||||
elif parts[0] == 'Ns':
|
||||
if self.ensure_mat('Ns'):
|
||||
self.crt_mat['specular_exponent'] = float(parts[1])
|
||||
elif parts[0] == 'd':
|
||||
if self.ensure_mat('d'):
|
||||
self.crt_mat['dissolved'] = float(parts[1])
|
||||
elif parts[0] == 'Tr':
|
||||
if self.ensure_mat('Tr'):
|
||||
self.crt_mat['dissolved'] = 1.0 - float(parts[1])
|
||||
elif parts[0] == 'Ni':
|
||||
if self.ensure_mat('Ni'):
|
||||
self.crt_mat['refraction_index'] = float(parts[1])
|
||||
elif parts[0] == 'illum':
|
||||
if self.ensure_mat('illum'):
|
||||
self.crt_mat['illumination_mode'] = int(parts[1])
|
||||
elif parts[0] == 'Pr':
|
||||
if self.ensure_mat('Pr'):
|
||||
self.crt_mat['roughness'] = float(parts[1])
|
||||
elif parts[0] == 'Pm':
|
||||
if self.ensure_mat('Pm'):
|
||||
self.crt_mat['metallic'] = float(parts[1])
|
||||
elif parts[0] == 'Ke':
|
||||
if self.ensure_mat('Ke'):
|
||||
self.crt_mat['emissive'] = [float(x) for x in parts[1:]]
|
||||
else:
|
||||
if parts[0] not in self.logged_unsupported:
|
||||
print('Unsupported MTL directive: ' + parts[0])
|
||||
self.logged_unsupported.add(parts[0])
|
||||
self.crt_mat = None
|
||||
|
||||
def get_material(self, name):
|
||||
return self.materials.get(name, None)
|
||||
@@ -0,0 +1,120 @@
|
||||
|
||||
from mtl_file_collection import MtlFileCollection
|
||||
import os
|
||||
|
||||
class ObjFile(object):
|
||||
def __init__(self, file_path) -> None:
|
||||
self.logged_unsupported = set()
|
||||
self.mtl_files = MtlFileCollection(os.path.dirname(file_path))
|
||||
|
||||
self.crt_object = ''
|
||||
self.crt_mtl = ''
|
||||
|
||||
self.vertices = []
|
||||
self.vertex_colors = []
|
||||
self.faces = []
|
||||
|
||||
# Constructed in the post-process phase
|
||||
self.objects = {}
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
while True:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
if not line.strip() or line.startswith('#'):
|
||||
continue
|
||||
parts = line.strip().split(' ')
|
||||
if parts[0] == 'v':
|
||||
self.on_v(parts[1:])
|
||||
elif parts[0] == 'l':
|
||||
self.on_l(parts[1:])
|
||||
elif parts[0] == 'f':
|
||||
self.on_f(parts[1:])
|
||||
elif parts[0] == 'mtllib':
|
||||
self.mtl_files.mtllib(' '.join(parts[1:]))
|
||||
elif parts[0] == 'usemtl':
|
||||
self.crt_mtl = ' '.join(parts[1:])
|
||||
elif parts[0] == 'o':
|
||||
self.crt_object = parts[1]
|
||||
else:
|
||||
if parts[0] not in self.logged_unsupported:
|
||||
print('Unsupported OBJ directive: ' + parts[0])
|
||||
self.logged_unsupported.add(parts[0])
|
||||
self.post_process()
|
||||
|
||||
def flatten_vertices(self):
|
||||
return [coord for point in self.vertices for coord in point]
|
||||
|
||||
def on_v(self, params):
|
||||
r, g, b = None, None, None
|
||||
w = 1.0
|
||||
if len(params) == 3:
|
||||
x, y, z = [float(param) for param in params]
|
||||
if len(params) == 4:
|
||||
x, y, z, w = [float(param) for param in params]
|
||||
if len(params) == 6:
|
||||
x, y, z, r, g, b = [float(param) for param in params]
|
||||
self.vertices.append((x, z, y))
|
||||
if r is None or g is None or b is None:
|
||||
self.vertex_colors.append(None)
|
||||
else:
|
||||
self.vertex_colors.append((r, g, b))
|
||||
|
||||
def on_l(self, params):
|
||||
# TODO: handle lines
|
||||
pass
|
||||
|
||||
def on_f(self, params):
|
||||
indices = []
|
||||
for param in params:
|
||||
# TODO: use texture coordinate index / use vertex normal index?
|
||||
v_index = int(param.split('/')[0])
|
||||
# If an index is positive then it refers to the offset in that vertex list, starting at 1.
|
||||
# If an index is negative then it relatively refers to the end of the vertex list, -1 referring to the last element.
|
||||
if v_index > 0:
|
||||
v_index -= 1
|
||||
indices.append(v_index)
|
||||
|
||||
self.faces.append({
|
||||
'indices': indices,
|
||||
'object': self.crt_object,
|
||||
'mtl': self.crt_mtl
|
||||
})
|
||||
|
||||
def post_process(self):
|
||||
# Step 1: group into object_id/material_id/[faces_with_global_indices]
|
||||
objects = {}
|
||||
for face in self.faces:
|
||||
if face['object'] not in objects:
|
||||
objects[face['object']] = {}
|
||||
obj = objects[face['object']]
|
||||
if face['mtl'] not in obj:
|
||||
obj[face['mtl']] = []
|
||||
obj[face['mtl']].append(face['indices'])
|
||||
|
||||
# Step 2: construct final structure: object_id / [{material, local_vertices, vertex_colors, faces_with_local_indices}]
|
||||
for object in objects:
|
||||
self.objects[object] = []
|
||||
for mtl in objects[object].keys():
|
||||
material = self.mtl_files.get_material(mtl)
|
||||
vertices = []
|
||||
vertex_colors = []
|
||||
faces = []
|
||||
|
||||
v_global2local_id = {}
|
||||
for face in objects[object][mtl]:
|
||||
for global_v in face:
|
||||
if global_v not in v_global2local_id:
|
||||
v_global2local_id[global_v] = len(vertices)
|
||||
vertices.append(self.vertices[global_v])
|
||||
vertex_colors.append(self.vertex_colors[global_v])
|
||||
|
||||
faces.append([v_global2local_id[global_id] for global_id in face])
|
||||
|
||||
self.objects[object].append({
|
||||
'material': material,
|
||||
'vertices': vertices,
|
||||
'vertex_colors': vertex_colors,
|
||||
'faces': faces
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
# Blender MTL File: 'untitled.blend'
|
||||
# Material Count: 5
|
||||
|
||||
newmtl Black
|
||||
Ns 96.078431
|
||||
Ka 0.000000 0.000000 0.000000
|
||||
Kd 0.066825 0.066825 0.066825
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
|
||||
newmtl Material.001
|
||||
Ns 96.078431
|
||||
Ka 0.000000 0.000000 0.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
|
||||
newmtl Material.002
|
||||
Ns 96.078431
|
||||
Ka 0.000000 0.000000 0.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
|
||||
newmtl Material.003
|
||||
Ns 96.078431
|
||||
Ka 0.000000 0.000000 0.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
|
||||
newmtl White
|
||||
Ns 96.078431
|
||||
Ka 0.000000 0.000000 0.000000
|
||||
Kd 0.640000 0.640000 0.640000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
File diff suppressed because it is too large
Load Diff
+4963
-4930
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@
|
||||
"knex": "^1.0.3",
|
||||
"node-fetch": "^2.6.5",
|
||||
"pg": "^8.7.1",
|
||||
"valid-filename": "^3.1.0",
|
||||
"web-ifc": "^0.0.33"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -8,10 +8,12 @@ const fs = require('fs')
|
||||
const { spawn } = require('child_process')
|
||||
|
||||
const ServerAPI = require('../ifc/api')
|
||||
const objDependencies = require('./objDependencies')
|
||||
|
||||
const HEALTHCHECK_FILE_PATH = '/tmp/last_successful_query'
|
||||
|
||||
const TMP_FILE_PATH = '/tmp/file_to_import'
|
||||
const TMP_INPUT_DIR = '/tmp/file_to_import'
|
||||
const TMP_FILE_PATH = '/tmp/file_to_import/file'
|
||||
const TMP_RESULTS_PATH = '/tmp/import_result.json'
|
||||
|
||||
let shouldExit = false
|
||||
@@ -55,6 +57,8 @@ async function doTask(task) {
|
||||
throw new Error('Internal error: DB inconsistent')
|
||||
}
|
||||
|
||||
fs.mkdirSync(TMP_INPUT_DIR, { recursive: true })
|
||||
|
||||
const upstreamFileStream = await getFileStream({ fileId: info.fileId })
|
||||
const diskFileStream = fs.createWriteStream(TMP_FILE_PATH)
|
||||
|
||||
@@ -103,6 +107,29 @@ async function doTask(task) {
|
||||
},
|
||||
10 * 60 * 1000
|
||||
)
|
||||
} else if (info.fileType === 'obj') {
|
||||
await objDependencies.downloadDependencies({
|
||||
objFilePath: TMP_FILE_PATH,
|
||||
streamId: info.streamId,
|
||||
destinationDir: TMP_INPUT_DIR
|
||||
})
|
||||
|
||||
await runProcessWithTimeout(
|
||||
'python3',
|
||||
[
|
||||
'-u',
|
||||
'./obj/import_file.py',
|
||||
TMP_FILE_PATH,
|
||||
info.userId,
|
||||
info.streamId,
|
||||
info.branchName,
|
||||
`File upload: ${info.fileName}`
|
||||
],
|
||||
{
|
||||
USER_TOKEN: tempUserToken
|
||||
},
|
||||
10 * 60 * 1000
|
||||
)
|
||||
} else {
|
||||
throw new Error(`File type ${info.fileType} is not supported`)
|
||||
}
|
||||
@@ -140,7 +167,7 @@ async function doTask(task) {
|
||||
)
|
||||
}
|
||||
|
||||
if (fs.existsSync(TMP_FILE_PATH)) fs.unlinkSync(TMP_FILE_PATH)
|
||||
fs.rmSync(TMP_INPUT_DIR, { force: true, recursive: true })
|
||||
if (fs.existsSync(TMP_RESULTS_PATH)) fs.unlinkSync(TMP_RESULTS_PATH)
|
||||
|
||||
if (tempUserToken) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
const knex = require('../knex')
|
||||
|
||||
module.exports = {
|
||||
async getFileInfoByName({ streamId, fileName }) {
|
||||
const { rows } = await knex.raw(
|
||||
`
|
||||
SELECT
|
||||
id as "fileId", "streamId", "branchName", "userId", "fileName", "fileType"
|
||||
FROM file_uploads
|
||||
WHERE "streamId" = ? AND "fileName" = ?
|
||||
ORDER BY "uploadDate" DESC
|
||||
LIMIT 1
|
||||
`,
|
||||
[streamId, fileName]
|
||||
)
|
||||
return rows[0]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
'use strict'
|
||||
const events = require('events')
|
||||
const fs = require('fs')
|
||||
const readline = require('readline')
|
||||
const path = require('path')
|
||||
|
||||
const { getFileInfoByName } = require('./filesMetadata')
|
||||
const { getFileStream } = require('./filesApi')
|
||||
|
||||
const isValidFilename = require('valid-filename')
|
||||
|
||||
async function tryDownloadFile({ fileName, streamId, destinationDir }) {
|
||||
if (!isValidFilename(fileName)) {
|
||||
console.log(`Invalid filename reference in OBJ dependencies: ${fileName}`)
|
||||
return false
|
||||
}
|
||||
|
||||
const fileInfo = await getFileInfoByName({ streamId, fileName })
|
||||
if (!fileInfo) {
|
||||
console.log(`OBJ dependency file not found in stream: ${fileName}`)
|
||||
return false
|
||||
}
|
||||
|
||||
const filePath = path.join(destinationDir, fileName)
|
||||
const upstreamFileStream = await getFileStream({ fileId: fileInfo.fileId })
|
||||
const diskFileStream = fs.createWriteStream(filePath)
|
||||
|
||||
upstreamFileStream.pipe(diskFileStream)
|
||||
await new Promise((fulfill) => diskFileStream.on('finish', fulfill))
|
||||
return true
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
async getReferencedMtlFiles({ objFilePath }) {
|
||||
const mtlFiles = []
|
||||
|
||||
try {
|
||||
const rl = readline.createInterface({
|
||||
input: fs.createReadStream(objFilePath),
|
||||
crlfDelay: Infinity
|
||||
})
|
||||
|
||||
rl.on('line', (line) => {
|
||||
if (line.startsWith('mtllib ')) {
|
||||
const mtlFile = line.slice('mtllib '.length).trim()
|
||||
mtlFiles.push(mtlFile)
|
||||
}
|
||||
})
|
||||
|
||||
await events.once(rl, 'close')
|
||||
} catch (err) {
|
||||
console.error(`Error getting dependencies for file ${objFilePath}: ${err}`)
|
||||
}
|
||||
return mtlFiles
|
||||
},
|
||||
|
||||
async downloadDependencies({ objFilePath, streamId, destinationDir }) {
|
||||
const dependencies = await this.getReferencedMtlFiles({ objFilePath })
|
||||
console.log(`Obj file depends on ${dependencies}`)
|
||||
for (const mtlFile of dependencies) {
|
||||
await tryDownloadFile({ fileName: mtlFile, streamId, destinationDir })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
<v-card-text>
|
||||
Speckle can now process files and store them as a commit (snapshot). You can
|
||||
then access it from the Speckle API, and receive it in other applications.
|
||||
Current supported formats are: IFC and STL. Thanks to the Open Source
|
||||
Current supported formats are: IFC, STL and OBJ. Thanks to the Open Source
|
||||
<a
|
||||
href="https://ifcjs.github.io/info/docs/Guide/web-ifc/Introduction"
|
||||
target="_blank"
|
||||
@@ -79,7 +79,7 @@
|
||||
<input
|
||||
id="myid"
|
||||
type="file"
|
||||
accept=".ifc,.IFC,.stl,.STL"
|
||||
accept=".ifc,.IFC,.stl,.STL,.obj,.OBJ,.mtl,.MTL"
|
||||
style="display: none"
|
||||
multiple
|
||||
@change="onFileSelect($event)"
|
||||
@@ -202,14 +202,17 @@ export default {
|
||||
this.dragError = null
|
||||
for (const file of files) {
|
||||
const extension = file.name.split('.')[1]
|
||||
if (!extension || !['ifc', 'stl'].includes(extension.toLowerCase())) {
|
||||
if (
|
||||
!extension ||
|
||||
!['ifc', 'stl', 'obj', 'mtl'].includes(extension.toLowerCase())
|
||||
) {
|
||||
this.dragError = `The ${extension.toLowerCase()} file extension is not yet supported`
|
||||
return
|
||||
}
|
||||
|
||||
if (file.size > 50626997) {
|
||||
if (file.size > 104857600) {
|
||||
this.dragError =
|
||||
'Your files are too powerful (for now). Maximum upload size is 50mb!'
|
||||
'Your files are too powerful (for now). Maximum upload size is 100mb!'
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ const {
|
||||
|
||||
const {
|
||||
checkBucket,
|
||||
uploadFile,
|
||||
startUploadFile,
|
||||
finishUploadFile,
|
||||
getFileInfo,
|
||||
getFileStream
|
||||
} = require('./services/fileuploads')
|
||||
@@ -125,7 +126,7 @@ exports.init = async (app) => {
|
||||
if (fileType === 'autodetect')
|
||||
fileType = filename.split('.').pop().toLowerCase()
|
||||
|
||||
const promise = uploadFile({
|
||||
const promise = startUploadFile({
|
||||
streamId: req.params.streamId,
|
||||
branchName: req.params.branchName || '',
|
||||
userId: req.context.userId,
|
||||
@@ -143,6 +144,9 @@ exports.init = async (app) => {
|
||||
const fileId = await promise
|
||||
fileIds.push(fileId)
|
||||
}
|
||||
for (const fileId of fileIds) {
|
||||
await finishUploadFile({ fileId })
|
||||
}
|
||||
res.send(fileIds)
|
||||
})
|
||||
|
||||
|
||||
@@ -60,7 +60,14 @@ module.exports = {
|
||||
return fileStream
|
||||
},
|
||||
|
||||
async uploadFile({ streamId, branchName, userId, fileName, fileType, fileStream }) {
|
||||
async startUploadFile({
|
||||
streamId,
|
||||
branchName,
|
||||
userId,
|
||||
fileName,
|
||||
fileType,
|
||||
fileStream
|
||||
}) {
|
||||
// Create ID and db entry
|
||||
const fileId = crs({ length: 10 })
|
||||
const dbFile = {
|
||||
@@ -81,12 +88,19 @@ module.exports = {
|
||||
|
||||
await s3.upload({ Bucket, Key, Body: fileStream }).promise()
|
||||
|
||||
return fileId
|
||||
},
|
||||
|
||||
async finishUploadFile({ fileId }) {
|
||||
const s3 = new S3(getS3Config())
|
||||
const Bucket = process.env.S3_BUCKET
|
||||
// TODO: error if missing
|
||||
const Key = `files/${fileId}`
|
||||
|
||||
// Get file size and update db entry
|
||||
const headResponse = await s3.headObject({ Key, Bucket }).promise()
|
||||
const fileSize = headResponse.ContentLength
|
||||
|
||||
await FileUploads().where({ id: fileId }).update({ uploadComplete: true, fileSize })
|
||||
|
||||
return fileId
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user