From c5c67a0de5e93f17644b9791101a7a5822d6aac0 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 9 Oct 2017 02:23:44 -0400 Subject: [PATCH 1/2] Minor refactors in doResolveUri() Add doResolveUris() Always call resolveUri() in FileTile and FileCard Before, these components would only try and resolve if claim info wasn't provided. Don't require uri param in lbry.resolve() It can now be "uris" instead, plus the error message about caching doesn't really apply anymore. Don't cache/cancel open resolve requests No longer needed because we're not doing resolve requests in bulk Add support for multiple URI resolution in lbry.resolve() Handle multi URL resolves with one action Update CHANGELOG.md --- CHANGELOG.md | 1 + ui/js/actions/content.js | 109 ++++++++++--------------- ui/js/component/fileCard/view.jsx | 14 +--- ui/js/component/fileTile/view.jsx | 8 +- ui/js/constants/action_types.js | 5 +- ui/js/lbry.js | 20 ++--- ui/js/page/discover/index.js | 3 +- ui/js/page/discover/view.jsx | 4 - ui/js/page/fileListDownloaded/index.js | 2 - ui/js/page/fileListDownloaded/view.jsx | 4 - ui/js/page/fileListPublished/index.js | 2 - ui/js/page/fileListPublished/view.jsx | 4 - ui/js/page/show/view.jsx | 10 +-- ui/js/reducers/claims.js | 29 +++---- ui/js/reducers/content.js | 27 ++---- 15 files changed, 89 insertions(+), 153 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c179bc4b3..c9e0a3984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * There is no longer a minimum channel length (#645) * Changed the File page to make it clearer how to to open the folder for a file * The upgrade message is now friendlier and includes a link to the release notes. + * Improved Discover page load time by batching all URIs into one API call ### Fixed * Improve layout (and implementation) of the icon panel in file tiles and cards diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index f377a131c..912b06d11 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -24,68 +24,46 @@ const { ipcRenderer } = require("electron"); const DOWNLOAD_POLL_INTERVAL = 250; -export function doResolveUri(uri) { +export function doResolveUris(uris) { return function(dispatch, getState) { - uri = lbryuri.normalize(uri); - + uris = uris.map(lbryuri.normalize); const state = getState(); - const alreadyResolving = selectResolvingUris(state).indexOf(uri) !== -1; - if (!alreadyResolving) { - dispatch({ - type: types.RESOLVE_URI_STARTED, - data: { uri }, - }); - - lbry.resolve({ uri }).then(resolutionInfo => { - const { claim, certificate } = resolutionInfo - ? resolutionInfo - : { claim: null, certificate: null }; - - dispatch({ - type: types.RESOLVE_URI_COMPLETED, - data: { - uri, - claim, - certificate, - }, - }); - }); - } - }; -} - -export function doCancelResolveUri(uri) { - return function(dispatch, getState) { - uri = lbryuri.normalize(uri); - - const state = getState(); - const alreadyResolving = selectResolvingUris(state).indexOf(uri) !== -1; - - if (alreadyResolving) { - lbry.cancelResolve({ uri }); - dispatch({ - type: types.RESOLVE_URI_CANCELED, - data: { - uri, - }, - }); - } - }; -} - -export function doCancelAllResolvingUris() { - return function(dispatch, getState) { - const state = getState(); + // Filter out URIs that are already resolving const resolvingUris = selectResolvingUris(state); - const actions = []; + const urisToResolve = uris.filter(uri => !resolvingUris.includes(uri)); - resolvingUris.forEach(uri => actions.push(doCancelResolveUri(uri))); + if (urisToResolve.length === 0) { + return; + } - dispatch(batchActions(...actions)); + dispatch({ + type: types.RESOLVE_URIS_STARTED, + data: { uris }, + }); + + let resolveInfo = {}; + lbry.resolve({ uris: urisToResolve }).then(result => { + for (let [uri, uriResolveInfo] of Object.entries(result)) { + const { claim, certificate } = uriResolveInfo || { + claim: null, + certificate: null, + }; + resolveInfo[uri] = { claim, certificate }; + } + + dispatch({ + type: types.RESOLVE_URIS_COMPLETED, + data: { resolveInfo }, + }); + }); }; } +export function doResolveUri(uri) { + return doResolveUris([uri]); +} + export function doFetchFeaturedUris() { return function(dispatch, getState) { const state = getState(); @@ -96,28 +74,27 @@ export function doFetchFeaturedUris() { const success = ({ Categories, Uris }) => { let featuredUris = {}; - const actions = []; - + let urisToResolve = []; Categories.forEach(category => { if (Uris[category] && Uris[category].length) { const uris = Uris[category]; featuredUris[category] = uris; - uris.forEach(uri => { - actions.push(doResolveUri(uri)); - }); + urisToResolve = [...urisToResolve, ...uris]; } }); - actions.push({ - type: types.FETCH_FEATURED_CONTENT_COMPLETED, - data: { - categories: Categories, - uris: featuredUris, - success: true, + const actions = [ + doResolveUris(urisToResolve), + { + type: types.FETCH_FEATURED_CONTENT_COMPLETED, + data: { + categories: Categories, + uris: featuredUris, + success: true, + }, }, - }); - + ]; dispatch(batchActions(...actions)); }; diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 3ff093ef0..9fc8bfd1a 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -20,19 +20,13 @@ class FileCard extends React.PureComponent { } componentWillMount() { - this.resolve(this.props); + const { uri, resolveUri } = this.props; + resolveUri(uri); } componentWillReceiveProps(nextProps) { - this.resolve(nextProps); - } - - resolve(props) { - const { isResolvingUri, resolveUri, claim, uri } = props; - - if (!isResolvingUri && claim === undefined && uri) { - resolveUri(uri); - } + const { uri, resolveUri } = nextProps; + resolveUri(uri); } handleMouseOver() { diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index b243ea1d6..daa7a011d 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -26,15 +26,15 @@ class FileTile extends React.PureComponent { } componentDidMount() { - const { isResolvingUri, claim, uri, resolveUri } = this.props; + const { uri, resolveUri } = this.props; - if (!isResolvingUri && !claim && uri) resolveUri(uri); + resolveUri(uri); } componentWillReceiveProps(nextProps) { - const { isResolvingUri, claim, uri, resolveUri } = this.props; + const { uri, resolveUri } = this.props; - if (!isResolvingUri && claim === undefined && uri) resolveUri(uri); + resolveUri(uri); } handleMouseOver() { diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 140509461..9a9143070 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -47,9 +47,8 @@ export const SUPPORT_TRANSACTION_FAILED = "SUPPORT_TRANSACTION_FAILED"; export const FETCH_FEATURED_CONTENT_STARTED = "FETCH_FEATURED_CONTENT_STARTED"; export const FETCH_FEATURED_CONTENT_COMPLETED = "FETCH_FEATURED_CONTENT_COMPLETED"; -export const RESOLVE_URI_STARTED = "RESOLVE_URI_STARTED"; -export const RESOLVE_URI_COMPLETED = "RESOLVE_URI_COMPLETED"; -export const RESOLVE_URI_CANCELED = "RESOLVE_URI_CANCELED"; +export const RESOLVE_URIS_STARTED = "RESOLVE_URIS_STARTED"; +export const RESOLVE_URIS_COMPLETED = "RESOLVE_URIS_COMPLETED"; export const FETCH_CHANNEL_CLAIMS_STARTED = "FETCH_CHANNEL_CLAIMS_STARTED"; export const FETCH_CHANNEL_CLAIMS_COMPLETED = "FETCH_CHANNEL_CLAIMS_COMPLETED"; export const FETCH_CHANNEL_CLAIM_COUNT_STARTED = diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 36f221afb..3d6c498a5 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -311,30 +311,24 @@ lbry.claim_list_mine = function(params = {}) { }); }; -lbry._resolveXhrs = {}; lbry.resolve = function(params = {}) { return new Promise((resolve, reject) => { - if (!params.uri) { - throw __("Resolve has hacked cache on top of it that requires a URI"); - } - lbry._resolveXhrs[params.uri] = apiCall( + apiCall( "resolve", params, function(data) { - resolve(data && data[params.uri] ? data[params.uri] : {}); + if ("uri" in params) { + // If only a single URI was requested, don't nest the results in an object + resolve(data && data[params.uri] ? data[params.uri] : {}); + } else { + resolve(data || {}); + } }, reject ); }); }; -lbry.cancelResolve = function(params = {}) { - const xhr = lbry._resolveXhrs[params.uri]; - if (xhr && xhr.readyState > 0 && xhr.readyState < 4) { - xhr.abort(); - } -}; - lbry = new Proxy(lbry, { get: function(target, name) { if (name in target) { diff --git a/ui/js/page/discover/index.js b/ui/js/page/discover/index.js index be49b892d..01f9add5e 100644 --- a/ui/js/page/discover/index.js +++ b/ui/js/page/discover/index.js @@ -1,6 +1,6 @@ import React from "react"; import { connect } from "react-redux"; -import { doFetchFeaturedUris, doCancelAllResolvingUris } from "actions/content"; +import { doFetchFeaturedUris } from "actions/content"; import { selectFeaturedUris, selectFetchingFeaturedUris, @@ -14,7 +14,6 @@ const select = state => ({ const perform = dispatch => ({ fetchFeaturedUris: () => dispatch(doFetchFeaturedUris()), - cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()), }); export default connect(select, perform)(DiscoverPage); diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index bc86a9597..19e00980d 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -204,10 +204,6 @@ class DiscoverPage extends React.PureComponent { this.props.fetchFeaturedUris(); } - componentWillUnmount() { - this.props.cancelResolvingUris(); - } - render() { const { featuredUris, fetchingFeaturedUris } = this.props; const hasContent = diff --git a/ui/js/page/fileListDownloaded/index.js b/ui/js/page/fileListDownloaded/index.js index 449d3f196..2da37ca9c 100644 --- a/ui/js/page/fileListDownloaded/index.js +++ b/ui/js/page/fileListDownloaded/index.js @@ -11,7 +11,6 @@ import { } from "selectors/claims"; import { doFetchClaimListMine } from "actions/content"; import { doNavigate } from "actions/navigation"; -import { doCancelAllResolvingUris } from "actions/content"; import FileListDownloaded from "./view"; const select = state => ({ @@ -25,7 +24,6 @@ const perform = dispatch => ({ navigate: path => dispatch(doNavigate(path)), fetchFileInfosDownloaded: () => dispatch(doFetchFileInfosAndPublishedClaims()), - cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()), fetchClaims: () => dispatch(doFetchClaimListMine()), }); diff --git a/ui/js/page/fileListDownloaded/view.jsx b/ui/js/page/fileListDownloaded/view.jsx index d970002ac..ceb0837e8 100644 --- a/ui/js/page/fileListDownloaded/view.jsx +++ b/ui/js/page/fileListDownloaded/view.jsx @@ -11,10 +11,6 @@ class FileListDownloaded extends React.PureComponent { if (!this.props.isFetching) this.props.fetchFileInfosDownloaded(); } - componentWillUnmount() { - this.props.cancelResolvingUris(); - } - render() { const { fileInfos, isFetching, navigate } = this.props; diff --git a/ui/js/page/fileListPublished/index.js b/ui/js/page/fileListPublished/index.js index c6bf2a43a..5b04e101e 100644 --- a/ui/js/page/fileListPublished/index.js +++ b/ui/js/page/fileListPublished/index.js @@ -8,7 +8,6 @@ import { } from "selectors/claims"; import { doClaimRewardType } from "actions/rewards"; import { doNavigate } from "actions/navigation"; -import { doCancelAllResolvingUris } from "actions/content"; import FileListPublished from "./view"; const select = state => ({ @@ -21,7 +20,6 @@ const perform = dispatch => ({ fetchClaims: () => dispatch(doFetchClaimListMine()), claimFirstPublishReward: () => dispatch(doClaimRewardType(rewards.TYPE_FIRST_PUBLISH)), - cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()), }); export default connect(select, perform)(FileListPublished); diff --git a/ui/js/page/fileListPublished/view.jsx b/ui/js/page/fileListPublished/view.jsx index 889fe8c70..0d9e093c8 100644 --- a/ui/js/page/fileListPublished/view.jsx +++ b/ui/js/page/fileListPublished/view.jsx @@ -14,10 +14,6 @@ class FileListPublished extends React.PureComponent { // if (this.props.claims.length > 0) this.props.fetchClaims(); } - componentWillUnmount() { - this.props.cancelResolvingUris(); - } - render() { const { claims, isFetching, navigate } = this.props; diff --git a/ui/js/page/show/view.jsx b/ui/js/page/show/view.jsx index 8b1e05789..3dd48a705 100644 --- a/ui/js/page/show/view.jsx +++ b/ui/js/page/show/view.jsx @@ -6,17 +6,15 @@ import FilePage from "page/file"; class ShowPage extends React.PureComponent { componentWillMount() { - const { isResolvingUri, resolveUri, uri } = this.props; + const { resolveUri, uri } = this.props; - if (!isResolvingUri) resolveUri(uri); + resolveUri(uri); } componentWillReceiveProps(nextProps) { - const { isResolvingUri, resolveUri, claim, uri } = nextProps; + const { resolveUri, uri } = nextProps; - if (!isResolvingUri && claim === undefined && uri) { - resolveUri(uri); - } + resolveUri(uri); } render() { diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 7f883a4ea..a3e4ae2b4 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -4,26 +4,27 @@ const reducers = {}; const defaultState = {}; -reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { - const { uri, certificate, claim } = action.data; - +reducers[types.RESOLVE_URIS_COMPLETED] = function(state, action) { + const { resolveInfo } = action.data; const byUri = Object.assign({}, state.claimsByUri); const byId = Object.assign({}, state.byId); - if (claim) { - byId[claim.claim_id] = claim; - byUri[uri] = claim.claim_id; - } else if (claim === undefined && certificate !== undefined) { - byId[certificate.claim_id] = certificate; - // Don't point URI at the channel certificate unless it actually is - // a channel URI. This is brittle. - if (!uri.split(certificate.name)[1].match(/\//)) { - byUri[uri] = certificate.claim_id; + for (let [uri, { certificate, claim }] of Object.entries(resolveInfo)) { + if (claim) { + byId[claim.claim_id] = claim; + byUri[uri] = claim.claim_id; + } else if (claim === undefined && certificate !== undefined) { + byId[certificate.claim_id] = certificate; + // Don't point URI at the channel certificate unless it actually is + // a channel URI. This is brittle. + if (!uri.split(certificate.name)[1].match(/\//)) { + byUri[uri] = certificate.claim_id; + } else { + byUri[uri] = null; + } } else { byUri[uri] = null; } - } else { - byUri[uri] = null; } return Object.assign({}, state, { diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index 56195e917..5afb2790a 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -31,34 +31,23 @@ reducers[types.FETCH_REWARD_CONTENT_COMPLETED] = function(state, action) { }); }; -reducers[types.RESOLVE_URI_STARTED] = function(state, action) { - const { uri } = action.data; +reducers[types.RESOLVE_URIS_STARTED] = function(state, action) { + let { uris } = action.data; const oldResolving = state.resolvingUris || []; const newResolving = Object.assign([], oldResolving); - if (newResolving.indexOf(uri) === -1) newResolving.push(uri); + + for (let uri of uris) { + if (!newResolving.includes(uri)) { + newResolving.push(uri); + } + } return Object.assign({}, state, { resolvingUris: newResolving, }); }; -reducers[types.RESOLVE_URI_CANCELED] = reducers[ - types.RESOLVE_URI_COMPLETED -] = function(state, action) { - const { uri } = action.data; - const resolvingUris = state.resolvingUris; - const index = state.resolvingUris.indexOf(uri); - const newResolvingUris = [ - ...resolvingUris.slice(0, index), - ...resolvingUris.slice(index + 1), - ]; - - return Object.assign({}, state, { - resolvingUris: newResolvingUris, - }); -}; - reducers[types.SET_PLAYING_URI] = (state, action) => { return Object.assign({}, state, { playingUri: action.data.uri, From cc1963d21e7debf45e7ce3d20f5348d18577c2f6 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 10 Oct 2017 09:02:18 -0400 Subject: [PATCH 2/2] restore resolve dispatch behavior on file cards and tiles --- ui/js/component/fileCard/view.jsx | 14 ++++++++++---- ui/js/component/fileTile/view.jsx | 8 ++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 9fc8bfd1a..3ff093ef0 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -20,13 +20,19 @@ class FileCard extends React.PureComponent { } componentWillMount() { - const { uri, resolveUri } = this.props; - resolveUri(uri); + this.resolve(this.props); } componentWillReceiveProps(nextProps) { - const { uri, resolveUri } = nextProps; - resolveUri(uri); + this.resolve(nextProps); + } + + resolve(props) { + const { isResolvingUri, resolveUri, claim, uri } = props; + + if (!isResolvingUri && claim === undefined && uri) { + resolveUri(uri); + } } handleMouseOver() { diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index daa7a011d..b243ea1d6 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -26,15 +26,15 @@ class FileTile extends React.PureComponent { } componentDidMount() { - const { uri, resolveUri } = this.props; + const { isResolvingUri, claim, uri, resolveUri } = this.props; - resolveUri(uri); + if (!isResolvingUri && !claim && uri) resolveUri(uri); } componentWillReceiveProps(nextProps) { - const { uri, resolveUri } = this.props; + const { isResolvingUri, claim, uri, resolveUri } = this.props; - resolveUri(uri); + if (!isResolvingUri && claim === undefined && uri) resolveUri(uri); } handleMouseOver() {