From 3b428c394c5037c3b68d797b5db575b4ae23074e Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 8 Mar 2017 02:17:16 -0500 Subject: [PATCH 1/2] Convert UI to use outpoints for unique IDs This covers *almost* everything. There are a couple of places that still use names or SD hashes because the APIs haven't been updated yet. --- ui/CHANGELOG.md | 1 + ui/js/component/file-actions.js | 18 ++++------ ui/js/component/file-tile.js | 22 ++++++------ ui/js/lbry.js | 64 ++++++++++++--------------------- ui/js/page/file-list.js | 64 ++++++++------------------------- ui/js/page/show.js | 11 +++--- ui/js/page/watch.js | 36 +++++++++++-------- 7 files changed, 84 insertions(+), 132 deletions(-) diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 05a416b4c..0226d5f88 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -10,6 +10,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ### Added * You can now make API calls directly on the lbry module, e.g. lbry.peer_list() * New-style API calls return promises instead of using callbacks + * Wherever possible, use outpoints for unique IDs instead of names or SD hashes ### Changed * diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index c5987dd4b..9a1d42af3 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -68,7 +68,7 @@ let FileActionsRow = React.createClass({ propTypes: { streamName: React.PropTypes.string, - sdHash: React.PropTypes.string.isRequired, + outpoint: React.PropTypes.string.isRequired, metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]), }, getInitialState: function() { @@ -145,11 +145,7 @@ let FileActionsRow = React.createClass({ }); }, handleRemoveConfirmed: function() { - if (this.props.streamName) { - lbry.removeFile(this.props.sdHash, this.props.streamName, this.state.deleteChecked); - } else { - alert('this file cannot be deleted because lbry is a retarded piece of shit'); - } + lbry.removeFile(this.props.outpoint, this.state.deleteChecked); this.setState({ modal: null, fileInfo: false, @@ -163,12 +159,12 @@ let FileActionsRow = React.createClass({ }, componentDidMount: function() { this._isMounted = true; - this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.sdHash, this.onFileInfoUpdate); + this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); }, componentWillUnmount: function() { this._isMounted = false; if (this._fileInfoSubscribeId) { - lbry.fileInfoUnsubscribe(this.props.sdHash, this._fileInfoSubscribeId); + lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); } }, render: function() { @@ -238,7 +234,7 @@ export let FileActions = React.createClass({ propTypes: { streamName: React.PropTypes.string, - sdHash: React.PropTypes.string.isRequired, + outpoint: React.PropTypes.string.isRequired, metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]), }, getInitialState: function() { @@ -262,7 +258,7 @@ export let FileActions = React.createClass({ }, componentDidMount: function() { this._isMounted = true; - this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.sdHash, this.onFileInfoUpdate); + this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); lbry.getStreamAvailability(this.props.streamName, (availability) => { if (this._isMounted) { this.setState({ @@ -290,7 +286,7 @@ export let FileActions = React.createClass({ return (
{ fileInfo || this.state.available || this.state.forceShowActions - ? + ? :
This file is not currently available.
- +

{isConfirmed @@ -168,7 +168,7 @@ export let FileTile = React.createClass({ getInitialState: function() { return { - sdHash: null, + outpoint: null, metadata: null } }, @@ -176,12 +176,12 @@ export let FileTile = React.createClass({ componentDidMount: function() { this._isMounted = true; - lbry.resolveName(this.props.name, (metadata) => { - if (this._isMounted && metadata) { + lbry.claim_show({name: this.props.name}).then(({value, txid, nout}) => { + if (this._isMounted && value) { // In case of a failed lookup, metadata will be null, in which case the component will never display this.setState({ - sdHash: metadata.sources.lbry_sd_hash, - metadata: metadata, + outpoint: txid + ':' + nout, + metadata: value, }); } }); @@ -190,10 +190,10 @@ export let FileTile = React.createClass({ this._isMounted = false; }, render: function() { - if (!this.state.metadata || !this.state.sdHash) { + if (!this.state.metadata || !this.state.outpoint) { return null; } - return ; + return ; } }); diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 7ac4105f3..7d598a833 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -209,26 +209,6 @@ lbry.getFeaturedDiscoverNames = function(callback) { }); } -lbry.getFileStatus = function(name, callback, errorCallback) { - lbry.call('get_lbry_file', { 'name': name }, callback, errorCallback); -} - -lbry.getFilesInfo = function(callback) { - lbry.call('get_lbry_files', {}, callback); -} - -lbry.getFileInfoByName = function(name, callback) { - lbry.call('get_lbry_file', {name: name}, callback); -} - -lbry.getFileInfoBySdHash = function(sdHash, callback) { - lbry.call('get_lbry_file', {sd_hash: sdHash}, callback); -} - -lbry.getFileInfoByFilename = function(filename, callback) { - lbry.call('get_lbry_file', {file_name: filename}, callback); -} - lbry.getMyClaims = function(callback) { lbry.call('get_name_claims', {}, callback); } @@ -241,14 +221,14 @@ lbry.stopFile = function(name, callback) { lbry.call('stop_lbry_file', { name: name }, callback); } -lbry.removeFile = function(sdHash, name, deleteTargetFile=true, callback) { // Name param is temporary until the API can delete by unique ID (SD hash, claim ID etc.) - this._removedFiles.push(sdHash); - this._updateSubscribedFileInfo(sdHash); +lbry.removeFile = function(outpoint, deleteTargetFile=true, callback) { + this._removedFiles.push(outpoint); + this._updateSubscribedFileInfo(outpoint); - lbry.call('delete_lbry_file', { - name: name, + lbry.file_delete({ + outpoint: outpoint, delete_target_file: deleteTargetFile, - }, callback); + }).then(callback); } lbry.getFileInfoWhenListed = function(name, callback, timeoutCallback, tryNum=0) { @@ -429,7 +409,7 @@ lbry._fileInfoSubscribeIdCounter = 0; lbry._fileInfoSubscribeCallbacks = {}; lbry._fileInfoSubscribeInterval = 5000; lbry._removedFiles = []; -lbry._claimIdOwnershipCache = {}; // should be claimId!!! But not +lbry._claimIdOwnershipCache = {}; lbry._updateClaimOwnershipCache = function(claimId) { lbry.getMyClaims((claimInfos) => { @@ -439,17 +419,17 @@ lbry._updateClaimOwnershipCache = function(claimId) { }); }; -lbry._updateSubscribedFileInfo = function(sdHash) { - const callSubscribedCallbacks = (sdHash, fileInfo) => { - for (let [subscribeId, callback] of Object.entries(this._fileInfoSubscribeCallbacks[sdHash])) { +lbry._updateSubscribedFileInfo = function(outpoint) { + const callSubscribedCallbacks = (outpoint, fileInfo) => { + for (let [subscribeId, callback] of Object.entries(this._fileInfoSubscribeCallbacks[outpoint])) { callback(fileInfo); } } - if (lbry._removedFiles.includes(sdHash)) { - callSubscribedCallbacks(sdHash, false); + if (lbry._removedFiles.includes(outpoint)) { + callSubscribedCallbacks(outpoint, false); } else { - lbry.getFileInfoBySdHash(sdHash, (fileInfo) => { + lbry.file_list({outpoint: outpoint}).then(([fileInfo]) => { if (fileInfo) { if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) { this._updateClaimOwnershipCache(fileInfo.claim_id); @@ -457,26 +437,26 @@ lbry._updateSubscribedFileInfo = function(sdHash) { fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id]; } - callSubscribedCallbacks(sdHash, fileInfo); + callSubscribedCallbacks(outpoint, fileInfo); }); } - if (Object.keys(this._fileInfoSubscribeCallbacks[sdHash]).length) { + if (Object.keys(this._fileInfoSubscribeCallbacks[outpoint]).length) { setTimeout(() => { - this._updateSubscribedFileInfo(sdHash); + this._updateSubscribedFileInfo(outpoint); }, lbry._fileInfoSubscribeInterval); } } -lbry.fileInfoSubscribe = function(sdHash, callback) { - if (!lbry._fileInfoSubscribeCallbacks[sdHash]) +lbry.fileInfoSubscribe = function(outpoint, callback) { + if (!lbry._fileInfoSubscribeCallbacks[outpoint]) { - lbry._fileInfoSubscribeCallbacks[sdHash] = {}; + lbry._fileInfoSubscribeCallbacks[outpoint] = {}; } const subscribeId = ++lbry._fileInfoSubscribeIdCounter; - lbry._fileInfoSubscribeCallbacks[sdHash][subscribeId] = callback; - lbry._updateSubscribedFileInfo(sdHash); + lbry._fileInfoSubscribeCallbacks[outpoint][subscribeId] = callback; + lbry._updateSubscribedFileInfo(outpoint); return subscribeId; } @@ -501,7 +481,7 @@ lbry = new Proxy(lbry, { return function(params={}) { return new Promise((resolve, reject) => { - jsonrpc.call(lbry.connectionString, name, [params], resolve, reject, reject); + jsonrpc.call(lbry.daemonConnectionString, name, [params], resolve, reject, reject); }); }; } diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index bff3b30ae..931e283e7 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -18,23 +18,15 @@ export let FileListDownloaded = React.createClass({ this._isMounted = true; document.title = "Downloaded Files"; - let publishedFilesSdHashes = []; - lbry.getMyClaims((claimInfos) => { - + lbry.claim_list_mine().then((myClaimInfos) => { if (!this._isMounted) { return; } - for (let claimInfo of claimInfos) { - let metadata = JSON.parse(claimInfo.value); - publishedFilesSdHashes.push(metadata.sources.lbry_sd_hash); - } - - lbry.getFilesInfo((fileInfos) => { + lbry.file_list().then((fileInfos) => { if (!this._isMounted) { return; } + const myClaimOutpoints = myClaimInfos.map(({txid, nOut}) => txid + ':' + nOut); this.setState({ - fileInfos: fileInfos.filter(({sd_hash}) => { - return publishedFilesSdHashes.indexOf(sd_hash) == -1; - }) + fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), }); }); }); @@ -74,41 +66,17 @@ export let FileListPublished = React.createClass({ this._isMounted = true; document.title = "Published Files"; - lbry.getMyClaims((claimInfos) => { - if (claimInfos.length == 0) { + lbry.claim_list_mine().then((claimInfos) => { + if (!this._isMounted) { return; } + + lbry.file_list().then((fileInfos) => { + if (!this._isMounted) { return; } + + const myClaimOutpoints = claimInfos.map(({txid, nOut}) => txid + ':' + nOut); this.setState({ - fileInfos: [], + fileInfos: fileInfos.filter(({outpoint}) => myClaimOutpoints.includes(outpoint)), }); - } - - /** - * Build newFileInfos as a sparse array and drop elements in at the same position they - * occur in claimInfos, so the order is preserved even if the API calls inside this loop - * return out of order. - */ - let newFileInfos = Array(claimInfos.length), - claimInfoProcessedCount = 0; - - for (let [i, claimInfo] of claimInfos.entries()) { - let metadata = JSON.parse(claimInfo.value); - lbry.getFileInfoBySdHash(metadata.sources.lbry_sd_hash, (fileInfo) => { - claimInfoProcessedCount++; - if (fileInfo !== false) { - newFileInfos[i] = fileInfo; - } - if (claimInfoProcessedCount >= claimInfos.length) { - /** - * newfileInfos may have gaps from claims that don't have associated files in - * lbrynet, so filter out any missing elements - */ - this.setState({ - fileInfos: newFileInfos.filter(function () { - return true - }), - }); - } - }); - } + }); }); }, render: function () { @@ -192,15 +160,13 @@ export let FileList = React.createClass({ seenUris = {}; const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); - for (let fileInfo of fileInfosSorted) { - let {lbry_uri, sd_hash, metadata} = fileInfo; - + for (let {lbry_uri, outpoint, metadata} of fileInfosSorted) { if (!metadata || seenUris[lbry_uri]) { continue; } seenUris[lbry_uri] = true; - content.push(); } diff --git a/ui/js/page/show.js b/ui/js/page/show.js index d469e426d..a2bb1b5f1 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -19,6 +19,7 @@ var FormatItem = React.createClass({ claimInfo: React.PropTypes.object, cost: React.PropTypes.number, name: React.PropTypes.string, + outpoint: React.PropTypes.string, costIncludesData: React.PropTypes.bool, }, render: function() { @@ -62,7 +63,7 @@ var FormatItem = React.createClass({

- +
@@ -120,9 +121,10 @@ var DetailPage = React.createClass({ componentWillMount: function() { document.title = 'lbry://' + this.props.name; - lbry.resolveName(this.props.name, (metadata) => { + lbry.claim_show({name: this.props.name}, ({name, txid, nout, value}) => { this.setState({ - metadata: metadata, + outpoint: txid + ':' + nout, + metadata: value, nameLookupComplete: true, }); }); @@ -143,12 +145,13 @@ var DetailPage = React.createClass({ const costIncludesData = this.state.costIncludesData; const metadata = this.state.metadata; const cost = this.state.cost; + const outpoint = this.state.outpoint; return (
{this.state.nameLookupComplete ? ( - + ) : (

No content

diff --git a/ui/js/page/watch.js b/ui/js/page/watch.js index fa2f70ef0..7d67ea56a 100644 --- a/ui/js/page/watch.js +++ b/ui/js/page/watch.js @@ -12,6 +12,7 @@ var WatchPage = React.createClass({ _isMounted: false, _controlsHideDelay: 3000, // Note: this needs to be shorter than the built-in delay in Electron, or Electron will hide the controls before us _controlsHideTimeout: null, + _outpoint: null, propTypes: { name: React.PropTypes.string, @@ -26,8 +27,10 @@ var WatchPage = React.createClass({ }; }, componentDidMount: function() { - lbry.getStream(this.props.name); - this.updateLoadStatus(); + lbry.get({name: this.props.name}, (fileInfo) => { + this._outpoint = fileInfo.outpoint; + this.updateLoadStatus(); + }); }, handleBackClicked: function() { history.back(); @@ -64,10 +67,13 @@ var WatchPage = React.createClass({ } }, updateLoadStatus: function() { - lbry.getFileStatus(this.props.name, (status) => { + api.file_list({ + outpoint: this._outpoint, + full_status: true, + }, ([status]) => { if (!status || !['running', 'stopped'].includes(status.code) || status.written_bytes == 0) { // Download hasn't started yet, so update status message (if available) then try again - // TODO: Would be nice to check if we have the MOOV before starting playing + // TODO: Would be nice to check if we have the MOOV before starting playing if (status) { this.setState({ loadStatusMessage: status.message @@ -79,17 +85,17 @@ var WatchPage = React.createClass({ readyToPlay: true, mimeType: status.mime_type, }) - const mediaFile = { - createReadStream: function (opts) { - // Return a readable stream that provides the bytes - // between offsets "start" and "end" inclusive - console.log('Stream between ' + opts.start + ' and ' + opts.end + '.'); - return fs.createReadStream(status.download_path, opts) - } - } - var elem = this.refs.video; - var videostream = VideoStream(mediaFile, elem); - elem.play(); + const mediaFile = { + createReadStream: function (opts) { + // Return a readable stream that provides the bytes + // between offsets "start" and "end" inclusive + console.log('Stream between ' + opts.start + ' and ' + opts.end + '.'); + return fs.createReadStream(status.download_path, opts) + } + }; + var elem = this.refs.video; + var videostream = VideoStream(mediaFile, elem); + elem.play(); } }); }, From a5feefe294a60e443b3387d217841eb409d5f93a Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 9 Mar 2017 15:26:11 -0500 Subject: [PATCH 2/2] Use updated key name in file_list (was lbry_uri, now name) --- ui/js/page/file-list.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index 931e283e7..085449303 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -160,13 +160,13 @@ export let FileList = React.createClass({ seenUris = {}; const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); - for (let {lbry_uri, outpoint, metadata} of fileInfosSorted) { - if (!metadata || seenUris[lbry_uri]) { + for (let {name, outpoint, metadata} of fileInfosSorted) { + if (!metadata || seenUris[name]) { continue; } - seenUris[lbry_uri] = true; - content.push(); }