Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 134
Creator Node Track Upload Improvements#6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
e1e63dd7061eb18a16097b428e3e4affb3bc945574File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -15,26 +15,27 @@ const maxMemoryFileSize = parseInt(config.get('maxMemoryFileSizeBytes')) // Defa | ||
| const ALLOWED_UPLOAD_FILE_EXTENSIONS = config.get('allowedUploadFileExtensions') // default set in config.json | ||
| const AUDIO_MIME_TYPE_REGEX = /audio\/(.*)/ | ||
| /** (1) Add file to IPFS; (2) save file to disk; | ||
| * (3) pin file via IPFS; (4) save file ref to DB | ||
| /** | ||
| * (1) Add file to IPFS; (2) save file to disk; | ||
| * (3) pin file via IPFS; (4) save file ref to DB | ||
| * @dev - only call this function when file is not already stored to disk | ||
| * - if it is, then use saveFileToIPFSFromFS() | ||
| */ | ||
| async function saveFile (req, buffer) { | ||
| async function saveFileFromBuffer (req, buffer) { | ||
| // make sure user has authenticated before saving file | ||
| if (!req.userId) { | ||
| throw new Error('User must be authenticated to save a file') | ||
| } | ||
| const ipfs = req.app.get('ipfsAPI') | ||
| let multihash = await ipfs.files.add(buffer, { onlyHash: true }) | ||
| multihash = multihash[0].hash | ||
| const multihash = (await ipfs.add(buffer))[0].hash | ||
| const fileLocation = path.join(req.app.get('storagePath'), '/' + multihash) | ||
| await writeFile(fileLocation, buffer) | ||
| const dstPath = path.join(req.app.get('storagePath'), multihash) | ||
| // TODO(roneilr): switch to using the IPFS filestore below to avoid duplicating content | ||
| const filesAdded = await ipfs.files.add(buffer) | ||
| assert.strictEqual(multihash, filesAdded[0].hash) | ||
| await writeFile(dstPath, buffer) | ||
| // TODO: switch to using the IPFS filestore below to avoid duplicating content | ||
| await ipfs.pin.add(multihash) | ||
| // add reference to file to database | ||
| @@ -43,7 +44,7 @@ async function saveFile (req, buffer) { | ||
| cnodeUserUUID: req.userId, | ||
| multihash: multihash, | ||
| sourceFile: req.fileName, | ||
| storagePath: fileLocation | ||
| storagePath: dstPath | ||
| } | ||
| }) | ||
| @@ -53,6 +54,41 @@ async function saveFile (req, buffer) { | ||
| return { multihash: multihash, fileUUID: file.fileUUID } | ||
| } | ||
| /** | ||
| * Save file to IPFS given file path. | ||
| * - Add and pin file to IPFS. | ||
| * - Re-save file to disk under multihash. | ||
| * - Save reference to file in DB. | ||
| */ | ||
| async function saveFileToIPFSFromFS (req, srcPath) { | ||
| // make sure user has authenticated before saving file | ||
dmanjunath marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if (!req.userId) throw new Error('User must be authenticated to save a file') | ||
| const ipfs = req.app.get('ipfsAPI') | ||
| const multihash = (await ipfs.addFromFs(srcPath))[0].hash | ||
SidSethi marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| const dstPath = path.join(req.app.get('storagePath'), multihash) | ||
| // store segment file copy under multihash for easy future retrieval | ||
| fs.copyFileSync(srcPath, dstPath) | ||
| // TODO: switch to using the IPFS filestore below to avoid duplicating content | ||
| await ipfs.pin.add(multihash) | ||
| // add reference to file to database | ||
| const file = (await models.File.findOrCreate({ where: | ||
| { | ||
| cnodeUserUUID: req.userId, | ||
| multihash: multihash, | ||
| sourceFile: req.fileName, | ||
| storagePath: dstPath | ||
| } | ||
| }))[0].dataValues | ||
| req.logger.info(`\nAdded file: ${multihash} for fileUUID ${file.fileUUID} from sourceFile ${req.fileName}`) | ||
| return { multihash: multihash, fileUUID: file.fileUUID } | ||
| } | ||
| /** Save file to disk given IPFS multihash, and ensure is pinned. | ||
| * Steps: | ||
| * - If file already stored on disk, return immediately. | ||
| @@ -168,7 +204,7 @@ const trackDiskStorage = multer.diskStorage({ | ||
| destination: function (req, file, cb) { | ||
| // save file under randomly named folders to avoid collisions | ||
| const randomFileName = getUuid() | ||
| const fileDir = req.app.get('storagePath') + '/' + randomFileName | ||
| const fileDir = path.join(req.app.get('storagePath'), randomFileName) | ||
| // create directories for original file and segments | ||
| fs.mkdirSync(fileDir) | ||
| @@ -207,4 +243,4 @@ function getFileExtension (fileName) { | ||
| return (fileName.lastIndexOf('.') >= 0) ? fileName.substr(fileName.lastIndexOf('.')) : '' | ||
| } | ||
| module.exports = { saveFile, saveFileForMultihash, removeTrackFolder, upload, trackFileUpload } | ||
| module.exports = { saveFileFromBuffer, saveFileToIPFSFromFS, saveFileForMultihash, removeTrackFolder, upload, trackFileUpload } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,25 @@ | ||
| const fs = require('fs') | ||
| const path = require('path') | ||
| const { Buffer } = require('ipfs-http-client') | ||
| const ffmpeg = require('../ffmpeg') | ||
| const ffprobe = require('../ffprobe') | ||
| const models = require('../models') | ||
| const authMiddleware = require('../authMiddleware') | ||
| const nodeSyncMiddleware = require('../redis').nodeSyncMiddleware | ||
| const { saveFile, removeTrackFolder, trackFileUpload } = require('../fileManager') | ||
| const { saveFileFromBuffer, saveFileToIPFSFromFS, removeTrackFolder, trackFileUpload } = require('../fileManager') | ||
| const { handleResponse, successResponse, errorResponseBadRequest, errorResponseServerError } = require('../apiHelpers') | ||
| module.exports = function (app) { | ||
| // upload track segment files and make avail - will later be associated with Audius track | ||
| /** | ||
| * upload track segment files and make avail - will later be associated with Audius track | ||
| * @dev - currently stores each segment twice, once under random file UUID & once under IPFS multihash | ||
| * - this should be addressed eventually | ||
| */ | ||
| app.post('/track_content', authMiddleware, nodeSyncMiddleware, trackFileUpload.single('file'), handleResponse(async (req, res) => { | ||
| if (req.fileFilterError) { | ||
| // POST body is not a valid file type | ||
| return errorResponseBadRequest(req.fileFilterError) | ||
| } | ||
| if (req.fileFilterError) return errorResponseBadRequest(req.fileFilterError) | ||
| // create and save segments to disk | ||
| // create and save track file segments to disk | ||
| let segmentFilePaths | ||
| try { | ||
| segmentFilePaths = await ffmpeg.segmentFile(req, req.fileDir, req.fileName) | ||
| @@ -25,26 +28,35 @@ module.exports = function (app) { | ||
| return errorResponseServerError(err) | ||
| } | ||
| // for each path, read file into buffer and pass to saveFile | ||
| const files = [] | ||
| for (let path of segmentFilePaths) { | ||
| let absolutePath = req.fileDir + '/segments/' + path | ||
| let fileBuffer = fs.readFileSync(absolutePath) | ||
| let { multihash } = await saveFile(req, fileBuffer) | ||
| const duration = await ffprobe.getTrackDuration(absolutePath) | ||
| if (duration) files.push({ 'multihash': multihash, duration: duration }) | ||
| // for each path, call saveFile and get back multihash; return multihash + segment duration | ||
| // run all async ops in parallel as they are not independent | ||
| let saveFileProms = [] | ||
| let durationProms = [] | ||
| for (let filePath of segmentFilePaths) { | ||
| const absolutePath = path.join(req.fileDir, 'segments', filePath) | ||
| const saveFileProm = saveFileToIPFSFromFS(req, absolutePath) | ||
| const durationProm = ffprobe.getTrackDuration(absolutePath) | ||
| saveFileProms.push(saveFileProm) | ||
| durationProms.push(durationProm) | ||
| } | ||
| // Resolve all promises + process responses | ||
| const [saveFilePromResps, durationPromResps] = await Promise.all( | ||
| [saveFileProms, durationProms].map(promiseArray => Promise.all(promiseArray)) | ||
| ) | ||
| let trackSegments = saveFilePromResps.map((saveFileResp, i) => { | ||
| return { 'multihash': saveFileResp.multihash, 'duration': durationPromResps[i] } | ||
| }) | ||
SidSethi marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| // exclude 0-length segments that are sometimes outputted by ffmpeg segmentation | ||
| trackSegments = trackSegments.filter(trackSegment => trackSegment.duration) | ||
dmanjunath marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. SidSethi marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| return successResponse({ 'track_segments': files }) | ||
| return successResponse({ 'track_segments': trackSegments }) | ||
| })) | ||
| /** given track metadata object, create track and share track metadata with network | ||
| * - return on success: temporary ID of track | ||
| * - return on failure: error if linked segments have not already been created via POST /track_content | ||
| */ | ||
| app.post('/tracks', authMiddleware, nodeSyncMiddleware, handleResponse(async (req, res) => { | ||
| const ipfs = req.app.get('ipfsAPI') | ||
| // TODO - input validation | ||
| const metadataJSON = req.body | ||
| @@ -78,8 +90,8 @@ module.exports = function (app) { | ||
| } | ||
| // store metadata multihash | ||
| const metadataBuffer = ipfs.types.Buffer.from(JSON.stringify(metadataJSON)) | ||
| const { multihash, fileUUID } = await saveFile(req, metadataBuffer) | ||
| const metadataBuffer = Buffer.from(JSON.stringify(metadataJSON)) | ||
| const { multihash, fileUUID } = await saveFileFromBuffer(req, metadataBuffer) | ||
| // build track object for db storage | ||
| const trackObj = { | ||
| @@ -131,7 +143,7 @@ module.exports = function (app) { | ||
| return errorResponseBadRequest('Invalid track ID') | ||
| } | ||
| // TODO(roneilr): validate that provided blockchain ID is indeed associated with | ||
| // TODO: validate that provided blockchain ID is indeed associated with | ||
| // user wallet and metadata CID | ||
| await track.update({ | ||
| blockchainId: blockchainId | ||
| @@ -152,21 +164,19 @@ module.exports = function (app) { | ||
| // update a track | ||
| app.put('/tracks/:blockchainId', authMiddleware, nodeSyncMiddleware, handleResponse(async (req, res) => { | ||
| const ipfs = req.app.get('ipfsAPI') | ||
| const blockchainId = req.params.blockchainId | ||
| const cnodeUserUUID = req.userId | ||
| const track = await models.Track.findOne({ where: { blockchainId, cnodeUserUUID } }) | ||
| if (!track) return errorResponseBadRequest(`Could not find track with id ${blockchainId} owned by calling user`) | ||
| // TODO(roneilr, dmanjunath): do some validation on metadata given | ||
| // TODO: do some validation on metadata given | ||
| const metadataJSON = req.body | ||
| const metadataBuffer = ipfs.types.Buffer.from(JSON.stringify(metadataJSON)) | ||
| const metadataBuffer = Buffer.from(JSON.stringify(metadataJSON)) | ||
| // write to a new file so there's still a record of the old file | ||
| const { multihash, fileUUID } = await saveFile(req, metadataBuffer) | ||
| const { multihash, fileUUID } = await saveFileFromBuffer(req, metadataBuffer) | ||
| const coverArtFileMultihash = metadataJSON.cover_art | ||
| let coverArtFileUUID = null | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.