From 7a4e9ad656424495565eea76e78d693fe63c7b14 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 18 Apr 2017 15:14:42 -0400 Subject: [PATCH 1/2] Rename uri module and its functions --- ui/js/component/channel-indicator.js | 6 ++-- ui/js/component/file-actions.js | 10 +++---- ui/js/component/file-tile.js | 20 ++++++------- ui/js/lbry.js | 42 ++++++++++++++-------------- ui/js/{uri.js => lbryuri.js} | 14 +++++----- ui/js/page/discover.js | 10 +++---- ui/js/page/file-list.js | 10 +++---- ui/js/page/publish.js | 1 - ui/js/page/show.js | 4 +-- 9 files changed, 58 insertions(+), 59 deletions(-) rename ui/js/{uri.js => lbryuri.js} (92%) diff --git a/ui/js/component/channel-indicator.js b/ui/js/component/channel-indicator.js index 2f30d5755..db2f40995 100644 --- a/ui/js/component/channel-indicator.js +++ b/ui/js/component/channel-indicator.js @@ -1,6 +1,6 @@ import React from 'react'; import lbry from '../lbry.js'; -import uri from '../uri.js'; +import lbryuri from '../lbryuri.js'; import {Icon} from './common.js'; const UriIndicator = React.createClass({ @@ -11,7 +11,7 @@ const UriIndicator = React.createClass({ }, render: function() { - const uriObj = uri.parseLbryUri(this.props.uri); + const uriObj = lbryuri.parse(this.props.uri); if (!this.props.hasSignature || !uriObj.isChannel) { return Anonymous; @@ -19,7 +19,7 @@ const UriIndicator = React.createClass({ const channelUriObj = Object.assign({}, uriObj); delete channelUriObj.path; - const channelUri = uri.buildLbryUri(channelUriObj, false); + const channelUri = lbryuri.build(channelUriObj, false); let icon, modifier; if (this.props.signatureIsValid) { diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index 6c105ed75..21d25eb47 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -1,6 +1,6 @@ import React from 'react'; import lbry from '../lbry.js'; -import uri from '../uri.js'; +import lbryuri from '../lbryuri.js'; import {Link} from '../component/link.js'; import {Icon, FilePrice} from '../component/common.js'; import {Modal} from './modal.js'; @@ -156,8 +156,8 @@ let FileActionsRow = React.createClass({ linkBlock = ; } - const lbryUri = uri.normalizeLbryUri(this.props.uri); - const title = this.props.metadata ? this.props.metadata.title : lbryUri; + const uri = lbryuri.normalize(this.props.uri); + const title = this.props.metadata ? this.props.metadata.title : uri; return (
{this.state.fileInfo !== null || this.state.fileInfo.isMine @@ -170,7 +170,7 @@ let FileActionsRow = React.createClass({ : '' } - Are you sure you'd like to buy {title} for credits? + Are you sure you'd like to buy {title} for credits? @@ -178,7 +178,7 @@ let FileActionsRow = React.createClass({ - LBRY was unable to download the stream {lbryUri}. + LBRY was unable to download the stream {uri}.
- +
{ !this.props.hidePrice ? : null} - +

- + {title} @@ -184,12 +184,12 @@ export let FileCardStream = React.createClass({ return null; } - const lbryUri = uri.normalizeLbryUri(this.props.uri); + const uri = lbryuri.normalize(this.props.uri); const metadata = this.props.metadata; const isConfirmed = !!metadata; - const title = isConfirmed ? metadata.title : lbryUri; + const title = isConfirmed ? metadata.title : uri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; - const primaryUrl = '?show=' + lbryUri; + const primaryUrl = '?show=' + uri; return (
@@ -198,7 +198,7 @@ export let FileCardStream = React.createClass({
{title}
{ !this.props.hidePrice ? : null} -
diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 244b82142..adc8ba4b4 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -1,7 +1,7 @@ import lbryio from './lbryio.js'; import lighthouse from './lighthouse.js'; import jsonrpc from './jsonrpc.js'; -import uri from './uri.js'; +import lbryuri from './lbryuri.js'; import {getLocal, getSession, setSession, setLocal} from './utils.js'; const {remote} = require('electron'); @@ -12,19 +12,19 @@ const menu = remote.require('./menu/main-menu'); * needed to make a dummy claim or file info object. */ function savePendingPublish({name, channel_name}) { - let lbryUri; + let uri; if (channel_name) { - lbryUri = uri.buildLbryUri({name: channel_name, path: name}, false); + uri = lbryuri.build({name: channel_name, path: name}, false); } else { - lbryUri = uri.buildLbryUri({name: name}, false); + uri = lbryuri.build({name: name}, false); } const pendingPublishes = getLocal('pendingPublishes') || []; const newPendingPublish = { name, channel_name, - claim_id: 'pending_claim_' + lbryUri, - txid: 'pending_' + lbryUri, + claim_id: 'pending_claim_' + uri, + txid: 'pending_' + uri, nout: 0, - outpoint: 'pending_' + lbryUri + ':0', + outpoint: 'pending_' + uri + ':0', time: Date.now(), }; setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]); @@ -215,35 +215,35 @@ lbry.getPeersForBlobHash = function(blobHash, callback) { * from Lighthouse is included. */ lbry.costPromiseCache = {} -lbry.getCostInfo = function(lbryUri) { - if (lbry.costPromiseCache[lbryUri] === undefined) { - lbry.costPromiseCache[lbryUri] = new Promise((resolve, reject) => { +lbry.getCostInfo = function(uri) { + if (lbry.costPromiseCache[uri] === undefined) { + lbry.costPromiseCache[uri] = new Promise((resolve, reject) => { const COST_INFO_CACHE_KEY = 'cost_info_cache'; let costInfoCache = getSession(COST_INFO_CACHE_KEY, {}) function cacheAndResolve(cost, includesData) { - costInfoCache[lbryUri] = {cost, includesData}; + costInfoCache[uri] = {cost, includesData}; setSession(COST_INFO_CACHE_KEY, costInfoCache); resolve({cost, includesData}); } - if (!lbryUri) { + if (!uri) { return reject(new Error(`URI required.`)); } - if (costInfoCache[lbryUri] && costInfoCache[lbryUri].cost) { - return resolve(costInfoCache[lbryUri]) + if (costInfoCache[uri] && costInfoCache[uri].cost) { + return resolve(costInfoCache[uri]) } - function getCost(lbryUri, size) { - lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => { + function getCost(uri, size) { + lbry.stream_cost_estimate({uri, ... size !== null ? {size} : {}}).then((cost) => { cacheAndResolve(cost, size !== null); }, reject); } - function getCostGenerous(lbryUri) { + function getCostGenerous(uri) { // If generous is on, the calculation is simple enough that we might as well do it here in the front end - lbry.resolve({uri: lbryUri}).then((resolutionInfo) => { + lbry.resolve({uri: uri}).then((resolutionInfo) => { const fee = resolutionInfo.claim.value.stream.metadata.fee; if (fee === undefined) { cacheAndResolve(0, true); @@ -257,12 +257,12 @@ lbry.getCostInfo = function(lbryUri) { }); } - const uriObj = uri.parseLbryUri(lbryUri); + const uriObj = lbryuri.parse(uri); const name = uriObj.path || uriObj.name; lbry.settings_get({allow_cached: true}).then(({is_generous_host}) => { if (is_generous_host) { - return getCostGenerous(lbryUri); + return getCostGenerous(uri); } lighthouse.get_size_for_name(name).then((size) => { @@ -278,7 +278,7 @@ lbry.getCostInfo = function(lbryUri) { }); }); } - return lbry.costPromiseCache[lbryUri]; + return lbry.costPromiseCache[uri]; } lbry.getMyClaims = function(callback) { diff --git a/ui/js/uri.js b/ui/js/lbryuri.js similarity index 92% rename from ui/js/uri.js rename to ui/js/lbryuri.js index 67615f7b5..2a6009cd7 100644 --- a/ui/js/uri.js +++ b/ui/js/lbryuri.js @@ -1,7 +1,7 @@ const CHANNEL_NAME_MIN_LEN = 4; const CLAIM_ID_MAX_LEN = 40; -const uri = {}; +const lbryuri = {}; /** * Parses a LBRY name into its component parts. Throws errors with user-friendly @@ -16,7 +16,7 @@ const uri = {}; * - claimId (string, if present) * - path (string, if persent) */ -uri.parseLbryUri = function(lbryUri, requireProto=false) { +lbryuri.parse = function(uri, requireProto=false) { // Break into components. Empty sub-matches are converted to null const componentsRegex = new RegExp( '^((?:lbry:\/\/)?)' + // protocol @@ -24,7 +24,7 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) { '([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end) '(/?)(.*)' // path separator, path ); - const [proto, name, modSep, modVal, pathSep, path] = componentsRegex.exec(lbryUri).slice(1).map(match => match || null); + const [proto, name, modSep, modVal, pathSep, path] = componentsRegex.exec(uri).slice(1).map(match => match || null); // Validate protocol if (requireProto && !proto) { @@ -105,7 +105,7 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) { }; } -uri.buildLbryUri = function(uriObj, includeProto=true) { +lbryuri.build = function(uriObj, includeProto=true) { const {name, claimId, claimSequence, bidPosition, path} = uriObj; return (includeProto ? 'lbry://' : '') + name + @@ -117,8 +117,8 @@ uri.buildLbryUri = function(uriObj, includeProto=true) { /* Takes a parseable LBRY URI and converts it to standard, canonical format (currently this just * consists of making sure it has a lbry:// prefix) */ -uri.normalizeLbryUri = function(lbryUri) { - return uri.buildLbryUri(uri.parseLbryUri(lbryUri)); +lbryuri.normalize= function(uri) { + return lbryuri.build(lbryuri.parse(uri)); } -export default uri; +export default lbryuri; diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index fd0f25811..671f418fe 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -1,7 +1,7 @@ import React from 'react'; import lbry from '../lbry.js'; import lbryio from '../lbryio.js'; -import uri from '../uri.js'; +import lbryuri from '../lbryuri.js'; import lighthouse from '../lighthouse.js'; import {FileTile, FileTileStream} from '../component/file-tile.js'; import {Link} from '../component/link.js'; @@ -47,22 +47,22 @@ var SearchResults = React.createClass({ var rows = [], seenNames = {}; //fix this when the search API returns claim IDs for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of this.props.results) { - let lbryUri; + let uri; if (channel_name) { - lbryUri = uri.buildLbryUri({ + uri = lbryuri.build({ name: channel_name, path: name, claimId: channel_id, }); } else { - lbryUri = uri.buildLbryUri({ + uri = lbryuri.build({ name: name, claimId: claim_id, }) } rows.push( - + ); } return ( diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index e746f9d77..1fc8ca5b9 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -1,6 +1,6 @@ import React from 'react'; import lbry from '../lbry.js'; -import uri from '../uri.js'; +import lbryuri from '../lbryuri.js'; import {Link} from '../component/link.js'; import {FormField} from '../component/form.js'; import {FileTileStream} from '../component/file-tile.js'; @@ -196,14 +196,14 @@ export let FileList = React.createClass({ } - let fileUri; + let uri; if (!channel_name) { - fileUri = uri.buildLbryUri({name}); + uri = lbryuri.build({name}); } else { - fileUri = uri.buildLbryUri({name: channel_name, path: name}); + uri = lbryuri.build({name: channel_name, path: name}); } seenUris[name] = true; - content.push(); } diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index b424e07ee..13736f0db 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -1,6 +1,5 @@ import React from 'react'; import lbry from '../lbry.js'; -import uri from '../uri.js'; import {FormField, FormRow} from '../component/form.js'; import {Link} from '../component/link.js'; import rewards from '../rewards.js'; diff --git a/ui/js/page/show.js b/ui/js/page/show.js index ea41731af..cc2fb5cfc 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -1,7 +1,7 @@ import React from 'react'; import lbry from '../lbry.js'; import lighthouse from '../lighthouse.js'; -import uri from '../uri.js'; +import lbryuri from '../lbryuri.js'; import {Video} from '../page/watch.js' import {TruncatedText, Thumbnail, FilePrice, BusyMessage} from '../component/common.js'; import {FileActions} from '../component/file-actions.js'; @@ -59,7 +59,7 @@ let ShowPage = React.createClass({ }; }, componentWillMount: function() { - this._uri = uri.normalizeLbryUri(this.props.uri); + this._uri = lbryuri.normalize(this.props.uri); document.title = this._uri; lbry.resolve({uri: this._uri}).then(({ claim: {txid, nout, has_signature, signature_is_valid, value: {stream: {metadata, source: {contentType}}}}}) => { From ca6d55da210b33c141a793912bcc48a4c1334b75 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 19 Apr 2017 13:56:17 -0400 Subject: [PATCH 2/2] Add special support for building channel claims in lbryuri module Extends lbryuri.build() and lbryuri.parse() to support special keys, contentName and channelName. These put the right values in the "name" and "path" position for both anonymous claims and channel content claims, which lets us write code that can deal with either type without special logic. --- CHANGELOG.md | 1 + ui/js/component/channel-indicator.js | 1 + ui/js/lbryuri.js | 72 ++++++++++++++++++++++------ ui/js/page/discover.js | 18 ++----- ui/js/page/file-list.js | 7 +-- 5 files changed, 65 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2002e1aad..ced55e42f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * New welcome flow for new users * Redesigned UI for Discover * Handle more of price calculations at the daemon layer to improve page load time + * Add special support for building channel claims in lbryuri module ### Changed * Update process now easier and more reliable diff --git a/ui/js/component/channel-indicator.js b/ui/js/component/channel-indicator.js index db2f40995..e19850c28 100644 --- a/ui/js/component/channel-indicator.js +++ b/ui/js/component/channel-indicator.js @@ -19,6 +19,7 @@ const UriIndicator = React.createClass({ const channelUriObj = Object.assign({}, uriObj); delete channelUriObj.path; + delete channelUriObj.contentName; const channelUri = lbryuri.build(channelUriObj, false); let icon, modifier; diff --git a/ui/js/lbryuri.js b/ui/js/lbryuri.js index 2a6009cd7..55a964e66 100644 --- a/ui/js/lbryuri.js +++ b/ui/js/lbryuri.js @@ -7,14 +7,23 @@ const lbryuri = {}; * Parses a LBRY name into its component parts. Throws errors with user-friendly * messages for invalid names. * + * N.B. that "name" indicates the value in the name position of the URI. For + * claims for channel content, this will actually be the channel name, and + * the content name is in the path (e.g. lbry://@channel/content) + * + * In most situations, you'll want to use the contentName and channelName keys + * and ignore the name key. + * * Returns a dictionary with keys: - * - name (string) - * - properName (string; strips off @ for channels) - * - isChannel (boolean) + * - name (string): The value in the "name" position in the URI. Note that this + * could be either content name or channel name; see above. + * - path (string, if persent) * - claimSequence (int, if present) * - bidPosition (int, if present) * - claimId (string, if present) - * - path (string, if persent) + * - isChannel (boolean) + * - contentName (string): For anon claims, the name; for channel claims, the path + * - channelName (string, if present): Channel name without @ */ lbryuri.parse = function(uri, requireProto=false) { // Break into components. Empty sub-matches are converted to null @@ -26,6 +35,8 @@ lbryuri.parse = function(uri, requireProto=false) { ); const [proto, name, modSep, modVal, pathSep, path] = componentsRegex.exec(uri).slice(1).map(match => match || null); + let contentName; + // Validate protocol if (requireProto && !proto) { throw new Error('LBRY URIs must include a protocol prefix (lbry://).'); @@ -36,20 +47,22 @@ lbryuri.parse = function(uri, requireProto=false) { throw new Error('URI does not include name.'); } - const isChannel = name[0] == '@'; - const properName = isChannel ? name.substr(1) : name; + const isChannel = name.startsWith('@'); + const channelName = isChannel ? name.slice(1) : name; if (isChannel) { - if (!properName) { + if (!channelName) { throw new Error('No channel name after @.'); } - if (properName.length < CHANNEL_NAME_MIN_LEN) { + if (channelName.length < CHANNEL_NAME_MIN_LEN) { throw new Error(`Channel names must be at least ${CHANNEL_NAME_MIN_LEN} characters.`); } + + contentName = path; } - const nameBadChars = properName.match(/[^A-Za-z0-9-]/g); + const nameBadChars = (channelName || name).match(/[^A-Za-z0-9-]/g); if (nameBadChars) { throw new Error(`Invalid character${nameBadChars.length == 1 ? '' : 's'} in name: ${nameBadChars.join(', ')}.`); } @@ -82,7 +95,7 @@ lbryuri.parse = function(uri, requireProto=false) { throw new Error('Bid position must be a number.'); } - // Validate path + // Validate and process path if (path) { if (!isChannel) { throw new Error('Only channel URIs may have a path.'); @@ -92,12 +105,16 @@ lbryuri.parse = function(uri, requireProto=false) { if (pathBadChars) { throw new Error(`Invalid character${count == 1 ? '' : 's'} in path: ${nameBadChars.join(', ')}`); } + + contentName = path; } else if (pathSep) { throw new Error('No path provided after /'); } return { - name, properName, isChannel, + name, path, isChannel, + ... contentName ? {contentName} : {}, + ... channelName ? {channelName} : {}, ... claimSequence ? {claimSequence: parseInt(claimSequence)} : {}, ... bidPosition ? {bidPosition: parseInt(bidPosition)} : {}, ... claimId ? {claimId} : {}, @@ -105,20 +122,45 @@ lbryuri.parse = function(uri, requireProto=false) { }; } -lbryuri.build = function(uriObj, includeProto=true) { - const {name, claimId, claimSequence, bidPosition, path} = uriObj; +/** + * Takes an object in the same format returned by lbryuri.parse() and builds a URI. + * + * The channelName key will accept names with or without the @ prefix. + */ +lbryuri.build = function(uriObj, includeProto=true, allowExtraProps=false) { + let {name, claimId, claimSequence, bidPosition, path, contentName, channelName} = uriObj; + + if (channelName) { + const channelNameFormatted = channelName.startsWith('@') ? channelName : '@' + channelName; + if (!name) { + name = channelNameFormatted; + } else if (name !== channelNameFormatted) { + throw new Error('Received a channel content URI, but name and channelName do not match. "name" represents the value in the name position of the URI (lbry://name...), which for channel content will be the channel name. In most cases, to construct a channel URI you should just pass channelName and contentName.'); + } + } + + if (contentName) { + if (!path) { + path = contentName; + } else if (path !== contentName) { + throw new Error('path and contentName do not match. Only one is required; most likely you wanted contentName.'); + } + } return (includeProto ? 'lbry://' : '') + name + (claimId ? `#${claimId}` : '') + (claimSequence ? `:${claimSequence}` : '') + (bidPosition ? `\$${bidPosition}` : '') + (path ? `/${path}` : ''); + } /* Takes a parseable LBRY URI and converts it to standard, canonical format (currently this just - * consists of making sure it has a lbry:// prefix) */ + * consists of adding the lbry:// prefix if needed) */ lbryuri.normalize= function(uri) { - return lbryuri.build(lbryuri.parse(uri)); + const {name, path, bidPosition, claimSequence, claimId} = lbryuri.parse(uri); + return lbryuri.build({name, path, claimSequence, bidPosition, claimId}); } +window.lbryuri = lbryuri; export default lbryuri; diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index 671f418fe..dc2811cfd 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -47,19 +47,11 @@ var SearchResults = React.createClass({ var rows = [], seenNames = {}; //fix this when the search API returns claim IDs for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of this.props.results) { - let uri; - if (channel_name) { - uri = lbryuri.build({ - name: channel_name, - path: name, - claimId: channel_id, - }); - } else { - uri = lbryuri.build({ - name: name, - claimId: claim_id, - }) - } + const uri = lbryuri.build({ + channelName: channel_name, + contentName: name, + claimId: channel_id || claim_id, + }); rows.push( diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index 1fc8ca5b9..063730e7f 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -196,12 +196,7 @@ export let FileList = React.createClass({ } - let uri; - if (!channel_name) { - uri = lbryuri.build({name}); - } else { - uri = lbryuri.build({name: channel_name, path: name}); - } + const uri = lbryuri.build({contentName: name, channelName: channel_name}); seenUris[name] = true; content.push(