diff --git a/controllers/publishController.js b/controllers/publishController.js index fc632836..64c97c3c 100644 --- a/controllers/publishController.js +++ b/controllers/publishController.js @@ -4,12 +4,10 @@ const lbryApi = require('../helpers/libraries/lbryApi.js'); const config = require('config'); const walledAddress = config.get('WalletConfig.lbryAddress'); const errorHandlers = require('../helpers/libraries/errorHandlers.js'); +const db = require('../models'); function createPublishParams (claim, filePath, license, nsfw) { logger.debug(`Creating Publish Parameters for "${claim}"`); - if (typeof nsfw === 'string') { - nsfw = (nsfw.toLowerCase() === 'on'); - } const publishParams = { name : claim, file_path: filePath, @@ -36,24 +34,61 @@ function deleteTemporaryFile (filePath) { }); } +function upsert (Model, values, condition) { + return Model + .findOne({ where: condition }) + .then(function (obj) { + if (obj) { // update + return obj.update(values); + } else { // insert + return Model.create(values); + } + }).catch(function (error) { + logger.error('Sequelize findOne error', error); + }); +} + module.exports = { - publish (claim, fileName, filePath, fileType, license, nsfw, socket, visitor) { + publish (name, fileName, filePath, fileType, license, nsfw, socket, visitor) { + // validate nsfw + if (typeof nsfw === 'string') { + nsfw = (nsfw.toLowerCase() === 'on'); + } // update the client socket.emit('publish-status', 'Your image is being published (this might take a second)...'); // send to analytics visitor.event('Publish Route', 'Publish Request', filePath).send(); // create the publish object - const publishParams = createPublishParams(claim, filePath, license, nsfw); + const publishParams = createPublishParams(name, filePath, license, nsfw); // get a promise to publish lbryApi .publishClaim(publishParams, fileName, fileType) .then(result => { - logger.info(`Successfully published ${fileName}`); + logger.info(`Successfully published ${fileName}`, result); + // google analytics visitor.event('Publish Route', 'Publish Success', filePath).send(); - socket.emit('publish-complete', { name: claim, result }); + // update old record of create new one + upsert( + db.File, + { + name, + claimId : result.claim_id, + outpoint: `${result.txid}:${result.nout}`, + fileName, + filePath, + fileType, + nsfw, + }, + { name, claimId: result.claim_id } + ).catch(error => { + logger.error('Sequelize findOne error', error); + }); + // update client + socket.emit('publish-complete', { name, result }); }) .catch(error => { logger.error(`Error publishing ${fileName}`, error); + // google analytics visitor.event('Publish Route', 'Publish Failure', filePath).send(); socket.emit('publish-failure', errorHandlers.handlePublishError(error)); deleteTemporaryFile(filePath); diff --git a/controllers/serveController.js b/controllers/serveController.js index e00daf46..e47a18b0 100644 --- a/controllers/serveController.js +++ b/controllers/serveController.js @@ -19,6 +19,61 @@ function getClaimAndHandleResponse (claimUri, resolve, reject) { }); } +function resolveAndUpdateIfNeeded (uri, claimName, claimId, outpoint) { + logger.debug('initiating resolve on local record to check outpoint'); + // 1. resolve claim + lbryApi + .resolveUri(uri) + .then(result => { + // logger.debug('resolved result:', result); + const resolvedOutpoint = `${result[uri].claim.txid}:${result[uri].claim.nout}`; + logger.debug('database outpoint:', outpoint); + logger.debug('resolved outpoint:', resolvedOutpoint); + // 2. if the outpoint's match, no further work needed + if (outpoint === resolvedOutpoint) { + logger.debug(`local outpoint matched`); + // 2. if the outpoints don't match, get claim and update records + } else { + logger.debug(`local outpoint did not match for ${uri}. Initiating update.`); + getClaimAndUpdate(uri, claimName, claimId); + } + }) + .catch(error => { + logger.error(`error resolving ${uri}`, error); + }); +} + +function getClaimAndUpdate (uri, claimName, claimId) { + // 1. get the claim + lbryApi + .getClaim(uri) + .then(({ outpoint, file_name, download_path, mime_type }) => { + logger.debug('getClaim outpoint', outpoint); + // 2. update the entry in db + db.File + .update({ + outpoint: outpoint, + fileName: file_name, + filePath: download_path, + fileType: mime_type, + }, { + where: { + name : claimName, + claimId: claimId, + }, + }) + .then(result => { + logger.debug('successfully updated mysql record', result); + }) + .catch(error => { + logger.error('sequelize error', error); + }); + }) + .catch(error => { + logger.error(`error while getting claim for ${uri}`, error); + }); +} + module.exports = { getClaimByName (claimName) { const deferred = new Promise((resolve, reject) => { @@ -51,6 +106,7 @@ module.exports = { } }) .catch(error => { + logger.error('sequelize error', error); reject(error); }); }) @@ -63,39 +119,35 @@ module.exports = { getClaimByClaimId (claimName, claimId) { const deferred = new Promise((resolve, reject) => { const uri = `${claimName}#${claimId}`; - // resolve the Uri - lbryApi - .resolveUri(uri) // note: use 'spread' and make parallel with db.File.findOne() - .then(result => { - const resolvedOutpoint = `${result[uri].claim.txid}:${result[uri].claim.nout}`; - // check locally for the claim - db.File - .findOne({ where: { name: claimName, claimId: claimId } }) - .then(claim => { - // if a found locally... - if (claim) { - logger.debug(`A mysql record was found for ${claimId}`); - // if the outpoint's match return it - if (claim.dataValues.outpoint === resolvedOutpoint) { - logger.debug(`local outpoint matched for ${claimId}`); - resolve(claim.dataValues); - // if the outpoint's don't match, fetch updated claim - } else { - logger.debug(`local outpoint did not match for ${claimId}`); - getClaimAndHandleResponse(uri, resolve, reject); - } - // ... otherwise use daemon to retrieve it - } else { + // 1. check locally for the claim + db.File + .findOne({ where: { name: claimName, claimId: claimId } }) + .then(claim => { + // 2. if a found locally, serve + if (claim) { + logger.debug(`A mysql record was found for ${claimId}`); + // trigger an update if needed + resolveAndUpdateIfNeeded(uri, claimName, claimId, claim.dataValues.outpoint); // ok to just start asynch function, or need to add to a task cue? + // resolve and send + resolve(claim.dataValues); + // 2. otherwise use daemon to retrieve it + } else { + // 3. resolve the Uri + lbryApi + .resolveUri(uri) + .then(result => { + // 4. check to see if the claim is free & public if (isFreePublicClaim(result[uri].claim)) { + // 5. get claim and serve getClaimAndHandleResponse(uri, resolve, reject); } else { reject('NO_FREE_PUBLIC_CLAIMS'); } - } - }) - .catch(error => { - reject(error); - }); + }) + .catch(error => { + reject(error); + }); + } }) .catch(error => { reject(error); diff --git a/helpers/libraries/lbryApi.js b/helpers/libraries/lbryApi.js index 7eb95fcc..3ff86cfd 100644 --- a/helpers/libraries/lbryApi.js +++ b/helpers/libraries/lbryApi.js @@ -1,15 +1,6 @@ const axios = require('axios'); -const db = require('../../models'); const logger = require('winston'); -function createFilesRecord (name, claimId, outpoint, fileName, filePath, fileType, nsfw) { - db.File - .create({ name, claimId, outpoint, fileName, filePath, fileType, nsfw }) - .catch(error => { - logger.error(`Sequelize File.create error`, error); - }); -} - module.exports = { publishClaim (publishParams, fileName, fileType) { logger.debug(`Publishing claim for "${fileName}"`); @@ -21,8 +12,6 @@ module.exports = { }) .then(response => { const result = response.data.result; - createFilesRecord( - publishParams.name, result.claim_id, `${result.txid}:${result.nout}`, fileName, publishParams.file_path, fileType, publishParams.metadata.nsfw); resolve(result); }) .catch(error => { @@ -50,11 +39,7 @@ module.exports = { /* note: put in a check to make sure we do not resolve until the download is actually complete (response.data.completed === true) */ - // save a record of the file to the Files table - const result = data.result; - createFilesRecord( - result.name, result.claim_id, result.outpoint, result.file_name, result.download_path, result.mime_type, result.metadata.stream.metadata.nsfw); - resolve(result); + resolve(data.result); }) .catch(error => { reject(error); @@ -88,6 +73,11 @@ module.exports = { params: { uri }, }) .then(({ data }) => { + // check for errors + if (data.result[uri].error) { + reject(data.result[uri].error); + return; + } resolve(data.result); }) .catch(error => { diff --git a/models/index.js b/models/index.js index 74e31518..0b64bb69 100644 --- a/models/index.js +++ b/models/index.js @@ -14,10 +14,10 @@ const sequelize = new Sequelize(connectionUri, { sequelize .authenticate() .then(() => { - logger.info('Sequelize has has been established mysql connection successfully.'); + logger.info('Sequelize has established mysql connection successfully.'); }) .catch(err => { - logger.error('Sequelize was nable to connect to the database:', err); + logger.error('Sequelize was unable to connect to the database:', err); }); fs.readdirSync(__dirname).filter(file => file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js').forEach(file => {