Revamp API wrapper code #10

Merged
alexliebowitz merged 5 commits from new-api-wrapper into master 2017-03-10 09:57:43 +01:00
12 changed files with 235 additions and 269 deletions

2
lbryum

@ -1 +1 @@
Subproject commit 07882aa766acf296a494bd641d88397a27bea83a Subproject commit 65703001c7f199492c0aa5ef4dc976c8fde0fad5

View file

@ -8,9 +8,9 @@ Web UI version numbers should always match the corresponding version of LBRY App
## [Unreleased] ## [Unreleased]
### Added ### 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 ### Changed
* *

View file

@ -68,7 +68,7 @@ let FileActionsRow = React.createClass({
propTypes: { propTypes: {
streamName: React.PropTypes.string, streamName: React.PropTypes.string,
sdHash: React.PropTypes.string.isRequired, outpoint: React.PropTypes.string.isRequired,
metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]), metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]),
}, },
getInitialState: function() { getInitialState: function() {
@ -145,11 +145,7 @@ let FileActionsRow = React.createClass({
}); });
}, },
handleRemoveConfirmed: function() { handleRemoveConfirmed: function() {
if (this.props.streamName) { lbry.removeFile(this.props.outpoint, this.state.deleteChecked);
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');
}
this.setState({ this.setState({
modal: null, modal: null,
fileInfo: false, fileInfo: false,
@ -163,12 +159,12 @@ let FileActionsRow = React.createClass({
}, },
componentDidMount: function() { componentDidMount: function() {
this._isMounted = true; this._isMounted = true;
this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.sdHash, this.onFileInfoUpdate); this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
this._isMounted = false; this._isMounted = false;
if (this._fileInfoSubscribeId) { if (this._fileInfoSubscribeId) {
lbry.fileInfoUnsubscribe(this.props.sdHash, this._fileInfoSubscribeId); lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId);
} }
}, },
render: function() { render: function() {
@ -238,7 +234,7 @@ export let FileActions = React.createClass({
propTypes: { propTypes: {
streamName: React.PropTypes.string, streamName: React.PropTypes.string,
sdHash: React.PropTypes.string.isRequired, outpoint: React.PropTypes.string.isRequired,
metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]), metadata: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.string]),
}, },
getInitialState: function() { getInitialState: function() {
@ -262,7 +258,7 @@ export let FileActions = React.createClass({
}, },
componentDidMount: function() { componentDidMount: function() {
this._isMounted = true; 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) => { lbry.getStreamAvailability(this.props.streamName, (availability) => {
if (this._isMounted) { if (this._isMounted) {
this.setState({ this.setState({
@ -290,7 +286,7 @@ export let FileActions = React.createClass({
return (<section className="file-actions"> return (<section className="file-actions">
{ {
fileInfo || this.state.available || this.state.forceShowActions fileInfo || this.state.available || this.state.forceShowActions
? <FileActionsRow sdHash={this.props.sdHash} metadata={this.props.metadata} streamName={this.props.streamName} /> ? <FileActionsRow outpoint={this.props.outpoint} metadata={this.props.metadata} streamName={this.props.streamName} />
: <div> : <div>
<div className="button-set-item empty">This file is not currently available.</div> <div className="button-set-item empty">This file is not currently available.</div>
<ToolTip label="Why?" <ToolTip label="Why?"

View file

@ -58,7 +58,7 @@ export let FileTileStream = React.createClass({
propTypes: { propTypes: {
metadata: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]), metadata: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]),
sdHash: React.PropTypes.string, outpoint: React.PropTypes.string,
hideOnRemove: React.PropTypes.bool, hideOnRemove: React.PropTypes.bool,
hidePrice: React.PropTypes.bool, hidePrice: React.PropTypes.bool,
obscureNsfw: React.PropTypes.bool obscureNsfw: React.PropTypes.bool
@ -79,12 +79,12 @@ export let FileTileStream = React.createClass({
componentDidMount: function() { componentDidMount: function() {
this._isMounted = true; this._isMounted = true;
if (this.props.hideOnRemove) { if (this.props.hideOnRemove) {
lbry.fileInfoSubscribe(this.props.sdHash, this.onFileInfoUpdate); lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
} }
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
if (this._fileInfoSubscribeId) { if (this._fileInfoSubscribeId) {
lbry.fileInfoUnsubscribe(this.props.sdHash, this._fileInfoSubscribeId); lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId);
} }
}, },
onFileInfoUpdate: function(fileInfo) { onFileInfoUpdate: function(fileInfo) {
@ -135,7 +135,7 @@ export let FileTileStream = React.createClass({
</TruncatedText> </TruncatedText>
</a> </a>
</h3> </h3>
<FileActions streamName={this.props.name} sdHash={this.props.sdHash} metadata={metadata} /> <FileActions streamName={this.props.name} outpoint={this.props.outpoint} metadata={metadata} />
<p className="file-tile__description"> <p className="file-tile__description">
<TruncatedText lines={3}> <TruncatedText lines={3}>
{isConfirmed {isConfirmed
@ -168,7 +168,7 @@ export let FileTile = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
sdHash: null, outpoint: null,
metadata: null metadata: null
} }
}, },
@ -176,12 +176,12 @@ export let FileTile = React.createClass({
componentDidMount: function() { componentDidMount: function() {
this._isMounted = true; this._isMounted = true;
lbry.resolveName(this.props.name, (metadata) => { lbry.claim_show({name: this.props.name}).then(({value, txid, nout}) => {
if (this._isMounted && metadata) { if (this._isMounted && value) {
// In case of a failed lookup, metadata will be null, in which case the component will never display // In case of a failed lookup, metadata will be null, in which case the component will never display
this.setState({ this.setState({
sdHash: metadata.sources.lbry_sd_hash, outpoint: txid + ':' + nout,
metadata: metadata, metadata: value,
}); });
} }
}); });
@ -190,10 +190,10 @@ export let FileTile = React.createClass({
this._isMounted = false; this._isMounted = false;
}, },
render: function() { render: function() {
if (!this.state.metadata || !this.state.sdHash) { if (!this.state.metadata || !this.state.outpoint) {
return null; return null;
} }
return <FileTileStream sdHash={this.state.sdHash} metadata={this.state.metadata} {... this.props} />; return <FileTileStream outpoint={this.state.outpoint} metadata={this.state.metadata} {... this.props} />;
} }
}); });

73
ui/js/jsonrpc.js Normal file
View file

@ -0,0 +1,73 @@
const jsonrpc = {};
jsonrpc.call = function (connectionString, method, params, callback, errorCallback, connectFailedCallback, timeout) {
var xhr = new XMLHttpRequest;
if (typeof connectFailedCallback !== 'undefined') {
if (timeout) {
xhr.timeout = timeout;
}
xhr.addEventListener('error', function (e) {
connectFailedCallback(e);
});
xhr.addEventListener('timeout', function() {
connectFailedCallback(new Error('XMLHttpRequest connection timed out'));
})
}
xhr.addEventListener('load', function() {
var response = JSON.parse(xhr.responseText);
if (response.error) {
if (errorCallback) {
errorCallback(response.error);
} else {
var errorEvent = new CustomEvent('unhandledError', {
detail: {
connectionString: connectionString,
method: method,
params: params,
code: response.error.code,
message: response.error.message,
data: response.error.data
}
});
document.dispatchEvent(errorEvent)
}
} else if (callback) {
callback(response.result);
}
});
if (connectFailedCallback) {
xhr.addEventListener('error', function (event) {
connectFailedCallback(event);
});
} else {
xhr.addEventListener('error', function (event) {
var errorEvent = new CustomEvent('unhandledError', {
detail: {
connectionString: connectionString,
method: method,
params: params,
code: xhr.status,
message: 'Connection to API server failed'
}
});
document.dispatchEvent(errorEvent);
});
}
const counter = parseInt(sessionStorage.getItem('JSONRPCCounter') || 0);
xhr.open('POST', connectionString, true);
xhr.send(JSON.stringify({
'jsonrpc': '2.0',
'method': method,
'params': params,
'id': counter,
}));
sessionStorage.setItem('JSONRPCCounter', counter + 1);
};
export default jsonrpc;

View file

@ -1,9 +1,10 @@
import lighthouse from './lighthouse.js'; import lighthouse from './lighthouse.js';
import jsonrpc from './jsonrpc.js';
const {remote} = require('electron'); const {remote} = require('electron');
const menu = remote.require('./menu/main-menu'); const menu = remote.require('./menu/main-menu');
var lbry = { let lbry = {
isConnected: false, isConnected: false,
rootPath: '.', rootPath: '.',
daemonConnectionString: 'http://localhost:5279/lbryapi', daemonConnectionString: 'http://localhost:5279/lbryapi',
@ -22,74 +23,8 @@ var lbry = {
} }
}; };
lbry.jsonrpc_call = function (connectionString, method, params, callback, errorCallback, connectFailedCallback, timeout) {
var xhr = new XMLHttpRequest;
if (typeof connectFailedCallback !== 'undefined') {
if (timeout) {
xhr.timeout = timeout;
}
xhr.addEventListener('error', function (e) {
connectFailedCallback(e);
});
xhr.addEventListener('timeout', function() {
connectFailedCallback(new Error('XMLHttpRequest connection timed out'));
})
}
xhr.addEventListener('load', function() {
var response = JSON.parse(xhr.responseText);
if (response.error) {
if (errorCallback) {
errorCallback(response.error);
} else {
var errorEvent = new CustomEvent('unhandledError', {
detail: {
connectionString: connectionString,
method: method,
params: params,
code: response.error.code,
message: response.error.message,
data: response.error.data
}
});
document.dispatchEvent(errorEvent)
}
} else if (callback) {
callback(response.result);
}
});
if (connectFailedCallback) {
xhr.addEventListener('error', function (event) {
connectFailedCallback(event);
});
} else {
xhr.addEventListener('error', function (event) {
var errorEvent = new CustomEvent('unhandledError', {
detail: {
connectionString: connectionString,
method: method,
params: params,
code: xhr.status,
message: 'Connection to API server failed'
}
});
document.dispatchEvent(errorEvent);
});
}
xhr.open('POST', connectionString, true);
xhr.send(JSON.stringify({
'jsonrpc': '2.0',
'method': method,
'params': params,
'id': 0
}));
}
lbry.call = function (method, params, callback, errorCallback, connectFailedCallback) { lbry.call = function (method, params, callback, errorCallback, connectFailedCallback) {
lbry.jsonrpc_call(lbry.daemonConnectionString, method, [params], callback, errorCallback, connectFailedCallback); jsonrpc.call(lbry.daemonConnectionString, method, [params], callback, errorCallback, connectFailedCallback);
} }
@ -244,12 +179,10 @@ lbry.getCostInfoForName = function(name, callback, errorCallback) {
}, errorCallback); }, errorCallback);
} }
lighthouse.getSizeForName(name, (size) => { lighthouse.get_size_for_name(name).then((size) => {
getCostWithData(name, size, callback, errorCallback); getCostWithData(name, size, callback, errorCallback);
}, () => { }, () => {
getCostNoData(name, callback, errorCallback); getCostNoData(name, callback, errorCallback);
}, () => {
getCostNoData(name, callback, errorCallback);
}); });
} }
@ -276,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.getMyClaims = function(callback) {
lbry.call('get_name_claims', {}, callback); lbry.call('get_name_claims', {}, callback);
} }
@ -308,14 +221,14 @@ lbry.stopFile = function(name, callback) {
lbry.call('stop_lbry_file', { name: 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.) lbry.removeFile = function(outpoint, deleteTargetFile=true, callback) {
this._removedFiles.push(sdHash); this._removedFiles.push(outpoint);
this._updateSubscribedFileInfo(sdHash); this._updateSubscribedFileInfo(outpoint);
lbry.call('delete_lbry_file', { lbry.file_delete({
name: name, outpoint: outpoint,
delete_target_file: deleteTargetFile, delete_target_file: deleteTargetFile,
}, callback); }).then(callback);
} }
lbry.getFileInfoWhenListed = function(name, callback, timeoutCallback, tryNum=0) { lbry.getFileInfoWhenListed = function(name, callback, timeoutCallback, tryNum=0) {
@ -329,7 +242,7 @@ lbry.getFileInfoWhenListed = function(name, callback, timeoutCallback, tryNum=0)
// Calls callback with file info when it appears in the lbrynet file manager. // Calls callback with file info when it appears in the lbrynet file manager.
// If timeoutCallback is provided, it will be called if the file fails to appear. // If timeoutCallback is provided, it will be called if the file fails to appear.
lbry.getFileStatus(name, (fileInfo) => { lbry.file_list({name: name}).then(([fileInfo]) => {
if (fileInfo) { if (fileInfo) {
callback(fileInfo); callback(fileInfo);
} else { } else {
@ -496,7 +409,7 @@ lbry._fileInfoSubscribeIdCounter = 0;
lbry._fileInfoSubscribeCallbacks = {}; lbry._fileInfoSubscribeCallbacks = {};
lbry._fileInfoSubscribeInterval = 5000; lbry._fileInfoSubscribeInterval = 5000;
lbry._removedFiles = []; lbry._removedFiles = [];
lbry._claimIdOwnershipCache = {}; // should be claimId!!! But not lbry._claimIdOwnershipCache = {};
lbry._updateClaimOwnershipCache = function(claimId) { lbry._updateClaimOwnershipCache = function(claimId) {
lbry.getMyClaims((claimInfos) => { lbry.getMyClaims((claimInfos) => {
@ -506,17 +419,17 @@ lbry._updateClaimOwnershipCache = function(claimId) {
}); });
}; };
lbry._updateSubscribedFileInfo = function(sdHash) { lbry._updateSubscribedFileInfo = function(outpoint) {
const callSubscribedCallbacks = (sdHash, fileInfo) => { const callSubscribedCallbacks = (outpoint, fileInfo) => {
for (let [subscribeId, callback] of Object.entries(this._fileInfoSubscribeCallbacks[sdHash])) { for (let [subscribeId, callback] of Object.entries(this._fileInfoSubscribeCallbacks[outpoint])) {
callback(fileInfo); callback(fileInfo);
} }
} }
if (lbry._removedFiles.includes(sdHash)) { if (lbry._removedFiles.includes(outpoint)) {
callSubscribedCallbacks(sdHash, false); callSubscribedCallbacks(outpoint, false);
} else { } else {
lbry.getFileInfoBySdHash(sdHash, (fileInfo) => { lbry.file_list({outpoint: outpoint}).then(([fileInfo]) => {
if (fileInfo) { if (fileInfo) {
if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) { if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) {
this._updateClaimOwnershipCache(fileInfo.claim_id); this._updateClaimOwnershipCache(fileInfo.claim_id);
@ -524,26 +437,26 @@ lbry._updateSubscribedFileInfo = function(sdHash) {
fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id]; 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(() => { setTimeout(() => {
this._updateSubscribedFileInfo(sdHash); this._updateSubscribedFileInfo(outpoint);
}, lbry._fileInfoSubscribeInterval); }, lbry._fileInfoSubscribeInterval);
} }
} }
lbry.fileInfoSubscribe = function(sdHash, callback) { lbry.fileInfoSubscribe = function(outpoint, callback) {
if (!lbry._fileInfoSubscribeCallbacks[sdHash]) if (!lbry._fileInfoSubscribeCallbacks[outpoint])
{ {
lbry._fileInfoSubscribeCallbacks[sdHash] = {}; lbry._fileInfoSubscribeCallbacks[outpoint] = {};
} }
const subscribeId = ++lbry._fileInfoSubscribeIdCounter; const subscribeId = ++lbry._fileInfoSubscribeIdCounter;
lbry._fileInfoSubscribeCallbacks[sdHash][subscribeId] = callback; lbry._fileInfoSubscribeCallbacks[outpoint][subscribeId] = callback;
lbry._updateSubscribedFileInfo(sdHash); lbry._updateSubscribedFileInfo(outpoint);
return subscribeId; return subscribeId;
} }
@ -560,4 +473,18 @@ lbry.showMenuIfNeeded = function() {
sessionStorage.setItem('menuShown', chosenMenu); sessionStorage.setItem('menuShown', chosenMenu);
}; };
lbry = new Proxy(lbry, {
get: function(target, name) {
if (name in target) {
return target[name];
}
return function(params={}) {
return new Promise((resolve, reject) => {
jsonrpc.call(lbry.daemonConnectionString, name, [params], resolve, reject, reject);
});
};
}
});
export default lbry; export default lbry;

View file

@ -1,67 +1,64 @@
import lbry from './lbry.js'; import lbry from './lbry.js';
import jsonrpc from './jsonrpc.js';
var lighthouse = { const queryTimeout = 5000;
_query_timeout: 5000, const maxQueryTries = 5;
_max_query_tries: 5, const defaultServers = [
'http://lighthouse4.lbry.io:50005',
'http://lighthouse5.lbry.io:50005',
'http://lighthouse6.lbry.io:50005',
];
const path = '/';
servers: [ let server = null;
'http://lighthouse4.lbry.io:50005', let connectTryNum = 0;
'http://lighthouse5.lbry.io:50005',
'http://lighthouse6.lbry.io:50005',
],
path: '/',
call: function(method, params, callback, errorCallback, connectFailedCallback, timeout) { function getServers() {
const handleConnectFailed = function(tryNum=0) { return lbry.getClientSetting('useCustomLighthouseServers')
if (tryNum > lighthouse._max_query_tries) { ? lbry.getClientSetting('customLighthouseServers')
if (connectFailedCallback) { : defaultServers;
connectFailedCallback(); }
} else {
throw new Error(`Could not connect to Lighthouse server. Last server attempted: ${lighthouse.server}`);
}
} else {
lbry.call(method, params, callback, errorCallback, () => { handleConnectFailed(tryNum + 1) }, timeout);
}
}
// Set the Lighthouse server if it hasn't been set yet, or if the current server is not in function call(method, params, callback, errorCallback) {
// the current set of servers (most likely because of a settings change). if (connectTryNum > maxQueryTries) {
if (typeof lighthouse.server === 'undefined' || lighthouse.getServers().indexOf(lighthouse.server) == -1) { if (connectFailedCallback) {
lighthouse.chooseNewServer(); connectFailedCallback();
}
lbry.jsonrpc_call(this.server + this.path, method, params, callback, errorCallback,
() => { handleConnectFailed() }, timeout || lighthouse.query_timeout);
},
getServers: function() {
return lbry.getClientSetting('useCustomLighthouseServers')
? lbry.getClientSetting('customLighthouseServers')
: lighthouse.servers;
},
chooseNewServer: function() {
// Randomly choose a new Lighthouse server and switch to it. If a server is already set, this
// will choose a different one (used for switching servers after a failed query).
const servers = lighthouse.getServers();
let newServerChoices;
if (!lighthouse.server) {
newServerChoices = servers;
} else { } else {
newServerChoices = lighthouse.getServers().slice(); throw new Error(`Could not connect to Lighthouse server. Last server attempted: ${server}`);
newServerChoices.splice(newServerChoices.indexOf(lighthouse.server), 1);
} }
lighthouse.server = newServerChoices[Math.round(Math.random() * (newServerChoices.length - 1))];
},
search: function(query, callback, errorCallback, connectFailedCallback, timeout) {
lighthouse.call('search', [query], callback, errorCallback, connectFailedCallback, timeout);
},
getSizeForName: function(name, callback, errorCallback, connectFailedCallback, timeout) {
lighthouse.call('get_size_for_name', [name], callback, errorCallback, connectFailedCallback, timeout);
} }
};
/**
* Set the Lighthouse server if it hasn't been set yet, if the current server is not in current
* set of servers (most likely because of a settings change), or we're re-trying after a failed
* query.
*/
if (!server || !getServers().includes(server) || connectTryNum > 0) {
// If there's a current server, filter it out so we get a new one
const newServerChoices = server ? getServers().filter((s) => s != server) : getServers();
server = newServerChoices[Math.round(Math.random() * (newServerChoices.length - 1))];
}
jsonrpc.call(server + path, method, params, (response) => {
connectTryNum = 0;
callback(response);
}, (error) => {
connectTryNum = 0;
errorCallback(error);
}, () => {
connectTryNum++;
call(method, params, callback, errorCallback);
});
}
const lighthouse = new Proxy({}, {
get: function(target, name) {
return function(...params) {
return new Promise((resolve, reject) => {
call(name, params, resolve, reject);
});
};
},
});
export default lighthouse; export default lighthouse;

View file

@ -9,9 +9,7 @@ lbry.showMenuIfNeeded();
var init = function() { var init = function() {
window.lbry = lbry; window.lbry = lbry;
if (lbry.getClientSetting('debug')) { window.lighthouse = lighthouse;
window.lighthouse = lighthouse;
}
var canvas = document.getElementById('canvas'); var canvas = document.getElementById('canvas');
if (window.sessionStorage.getItem('loaded') == 'y') { if (window.sessionStorage.getItem('loaded') == 'y') {

View file

@ -125,7 +125,7 @@ var DiscoverPage = React.createClass({
query: query, query: query,
}); });
lighthouse.search(query, this.searchCallback); lighthouse.search(query).then(this.searchCallback);
}, },
componentWillMount: function() { componentWillMount: function() {

View file

@ -18,23 +18,15 @@ export let FileListDownloaded = React.createClass({
this._isMounted = true; this._isMounted = true;
document.title = "Downloaded Files"; document.title = "Downloaded Files";
let publishedFilesSdHashes = []; lbry.claim_list_mine().then((myClaimInfos) => {
lbry.getMyClaims((claimInfos) => {
if (!this._isMounted) { return; } if (!this._isMounted) { return; }
for (let claimInfo of claimInfos) { lbry.file_list().then((fileInfos) => {
let metadata = JSON.parse(claimInfo.value);
publishedFilesSdHashes.push(metadata.sources.lbry_sd_hash);
}
lbry.getFilesInfo((fileInfos) => {
if (!this._isMounted) { return; } if (!this._isMounted) { return; }
const myClaimOutpoints = myClaimInfos.map(({txid, nOut}) => txid + ':' + nOut);
this.setState({ this.setState({
fileInfos: fileInfos.filter(({sd_hash}) => { fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)),
return publishedFilesSdHashes.indexOf(sd_hash) == -1;
})
}); });
}); });
}); });
@ -74,41 +66,17 @@ export let FileListPublished = React.createClass({
this._isMounted = true; this._isMounted = true;
document.title = "Published Files"; document.title = "Published Files";
lbry.getMyClaims((claimInfos) => { lbry.claim_list_mine().then((claimInfos) => {
if (claimInfos.length == 0) { if (!this._isMounted) { return; }
lbry.file_list().then((fileInfos) => {
if (!this._isMounted) { return; }
const myClaimOutpoints = claimInfos.map(({txid, nOut}) => txid + ':' + nOut);
this.setState({ 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 () { render: function () {
@ -192,15 +160,13 @@ export let FileList = React.createClass({
seenUris = {}; seenUris = {};
const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos);
for (let fileInfo of fileInfosSorted) { for (let {name, outpoint, metadata} of fileInfosSorted) {
let {lbry_uri, sd_hash, metadata} = fileInfo; if (!metadata || seenUris[name]) {
if (!metadata || seenUris[lbry_uri]) {
continue; continue;
} }
seenUris[lbry_uri] = true; seenUris[name] = true;
content.push(<FileTileStream key={lbry_uri} name={lbry_uri} hideOnRemove={true} sdHash={sd_hash} content.push(<FileTileStream key={outpoint} outpoint={outpoint} name={name} hideOnRemove={true}
hidePrice={this.props.hidePrices} metadata={metadata} />); hidePrice={this.props.hidePrices} metadata={metadata} />);
} }

View file

@ -19,6 +19,7 @@ var FormatItem = React.createClass({
claimInfo: React.PropTypes.object, claimInfo: React.PropTypes.object,
cost: React.PropTypes.number, cost: React.PropTypes.number,
name: React.PropTypes.string, name: React.PropTypes.string,
outpoint: React.PropTypes.string,
costIncludesData: React.PropTypes.bool, costIncludesData: React.PropTypes.bool,
}, },
render: function() { render: function() {
@ -62,7 +63,7 @@ var FormatItem = React.createClass({
</tbody> </tbody>
</table> </table>
</section> </section>
<FileActions streamName={this.props.name} sdHash={claimInfo.sources.lbry_sd_hash} metadata={claimInfo} /> <FileActions streamName={this.props.name} outpoint={this.props.outpoint} metadata={claimInfo} />
<section> <section>
<Link href="https://lbry.io/dmca" label="report" className="button-text-help" /> <Link href="https://lbry.io/dmca" label="report" className="button-text-help" />
</section> </section>
@ -120,9 +121,10 @@ var DetailPage = React.createClass({
componentWillMount: function() { componentWillMount: function() {
document.title = 'lbry://' + this.props.name; 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({ this.setState({
metadata: metadata, outpoint: txid + ':' + nout,
metadata: value,
nameLookupComplete: true, nameLookupComplete: true,
}); });
}); });
@ -143,12 +145,13 @@ var DetailPage = React.createClass({
const costIncludesData = this.state.costIncludesData; const costIncludesData = this.state.costIncludesData;
const metadata = this.state.metadata; const metadata = this.state.metadata;
const cost = this.state.cost; const cost = this.state.cost;
const outpoint = this.state.outpoint;
return ( return (
<main> <main>
<section className="card"> <section className="card">
{this.state.nameLookupComplete ? ( {this.state.nameLookupComplete ? (
<FormatsSection name={name} claimInfo={metadata} cost={cost} costIncludesData={costIncludesData} /> <FormatsSection name={name} outpoint={outpoint} claimInfo={metadata} cost={cost} costIncludesData={costIncludesData} />
) : ( ) : (
<div> <div>
<h2>No content</h2> <h2>No content</h2>

View file

@ -12,6 +12,7 @@ var WatchPage = React.createClass({
_isMounted: false, _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 _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, _controlsHideTimeout: null,
_outpoint: null,
propTypes: { propTypes: {
name: React.PropTypes.string, name: React.PropTypes.string,
@ -26,8 +27,10 @@ var WatchPage = React.createClass({
}; };
}, },
componentDidMount: function() { componentDidMount: function() {
lbry.getStream(this.props.name); lbry.get({name: this.props.name}, (fileInfo) => {
this.updateLoadStatus(); this._outpoint = fileInfo.outpoint;
this.updateLoadStatus();
});
}, },
handleBackClicked: function() { handleBackClicked: function() {
history.back(); history.back();
@ -64,10 +67,13 @@ var WatchPage = React.createClass({
} }
}, },
updateLoadStatus: function() { 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) { 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 // 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) { if (status) {
this.setState({ this.setState({
loadStatusMessage: status.message loadStatusMessage: status.message
@ -79,17 +85,17 @@ var WatchPage = React.createClass({
readyToPlay: true, readyToPlay: true,
mimeType: status.mime_type, mimeType: status.mime_type,
}) })
const mediaFile = { const mediaFile = {
createReadStream: function (opts) { createReadStream: function (opts) {
// Return a readable stream that provides the bytes // Return a readable stream that provides the bytes
// between offsets "start" and "end" inclusive // between offsets "start" and "end" inclusive
console.log('Stream between ' + opts.start + ' and ' + opts.end + '.'); console.log('Stream between ' + opts.start + ' and ' + opts.end + '.');
return fs.createReadStream(status.download_path, opts) return fs.createReadStream(status.download_path, opts)
} }
} };
var elem = this.refs.video; var elem = this.refs.video;
var videostream = VideoStream(mediaFile, elem); var videostream = VideoStream(mediaFile, elem);
elem.play(); elem.play();
} }
}); });
}, },