Merge pull request #14 from lbryio/publish-cache
Make publishes appear immediately in My Files
This commit is contained in:
commit
b3aa61cf47
4 changed files with 200 additions and 11 deletions
|
@ -15,6 +15,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
|||
* 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
|
||||
* New publishes now display immediately in My Files, even before they hit the lbrynet file manager.
|
||||
|
||||
### Changed
|
||||
* Update process now easier and more reliable
|
||||
|
|
|
@ -179,7 +179,7 @@ let FileActionsRow = React.createClass({
|
|||
let linkBlock;
|
||||
if (this.state.fileInfo === false && !this.state.attemptingDownload) {
|
||||
linkBlock = <Link button="text" label="Download" icon="icon-download" onClick={this.onDownloadClick} />;
|
||||
} else if (this.state.attemptingDownload || !this.state.fileInfo.completed) {
|
||||
} else if (this.state.attemptingDownload || (!this.state.fileInfo.completed && !this.state.fileInfo.isMine)) {
|
||||
const
|
||||
progress = this.state.fileInfo ? this.state.fileInfo.written_bytes / this.state.fileInfo.total_bytes * 100 : 0,
|
||||
label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...',
|
||||
|
|
193
ui/js/lbry.js
193
ui/js/lbry.js
|
@ -1,15 +1,103 @@
|
|||
import lighthouse from './lighthouse.js';
|
||||
import jsonrpc from './jsonrpc.js';
|
||||
import {getLocal, setLocal} from './utils.js';
|
||||
|
||||
const {remote} = require('electron');
|
||||
const menu = remote.require('./menu/main-menu');
|
||||
|
||||
/**
|
||||
* Records a publish attempt in local storage. Returns a dictionary with all the data needed to
|
||||
* needed to make a dummy claim or file info object.
|
||||
*/
|
||||
function savePendingPublish(name) {
|
||||
const pendingPublishes = getLocal('pendingPublishes') || [];
|
||||
const newPendingPublish = {
|
||||
claim_id: 'pending_claim_' + name,
|
||||
txid: 'pending_' + name,
|
||||
nout: 0,
|
||||
outpoint: 'pending_' + name + ':0',
|
||||
name: name,
|
||||
time: Date.now(),
|
||||
};
|
||||
setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]);
|
||||
return newPendingPublish;
|
||||
}
|
||||
|
||||
function removePendingPublish({name, outpoint}) {
|
||||
setLocal('pendingPublishes', getPendingPublishes().filter(
|
||||
(pub) => pub.name != name && pub.outpoint != outpoint
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current list of pending publish attempts. Filters out any that have timed out and
|
||||
* removes them from the list.
|
||||
*/
|
||||
function getPendingPublishes() {
|
||||
const pendingPublishes = getLocal('pendingPublishes') || [];
|
||||
|
||||
const newPendingPublishes = [];
|
||||
for (let pendingPublish of pendingPublishes) {
|
||||
if (Date.now() - pendingPublish.time <= lbry.pendingPublishTimeout) {
|
||||
newPendingPublishes.push(pendingPublish);
|
||||
}
|
||||
}
|
||||
setLocal('pendingPublishes', newPendingPublishes);
|
||||
return newPendingPublishes
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pending publish attempt by its name or (fake) outpoint. If none is found (or one is found
|
||||
* but it has timed out), returns null.
|
||||
*/
|
||||
function getPendingPublish({name, outpoint}) {
|
||||
const pendingPublishes = getPendingPublishes();
|
||||
const pendingPublishIndex = pendingPublishes.findIndex(
|
||||
({name: itemName, outpoint: itemOutpoint}) => itemName == name || itemOutpoint == outpoint
|
||||
);
|
||||
const pendingPublish = pendingPublishes[pendingPublishIndex];
|
||||
|
||||
if (pendingPublishIndex == -1) {
|
||||
return null;
|
||||
} else if (Date.now() - pendingPublish.time > lbry.pendingPublishTimeout) {
|
||||
// Pending publish timed out, so remove it from the stored list and don't match
|
||||
|
||||
const newPendingPublishes = pendingPublishes.slice();
|
||||
newPendingPublishes.splice(pendingPublishIndex, 1);
|
||||
setLocal('pendingPublishes', newPendingPublishes);
|
||||
return null;
|
||||
} else {
|
||||
return pendingPublish;
|
||||
}
|
||||
}
|
||||
|
||||
function pendingPublishToDummyClaim({name, outpoint, claim_id, txid, nout}) {
|
||||
return {
|
||||
name: name,
|
||||
outpoint: outpoint,
|
||||
claim_id: claim_id,
|
||||
txid: txid,
|
||||
nout: nout,
|
||||
};
|
||||
}
|
||||
|
||||
function pendingPublishToDummyFileInfo({name, outpoint, claim_id}) {
|
||||
return {
|
||||
name: name,
|
||||
outpoint: outpoint,
|
||||
claim_id: claim_id,
|
||||
metadata: "Attempting publication",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
let lbry = {
|
||||
isConnected: false,
|
||||
rootPath: '.',
|
||||
daemonConnectionString: 'http://localhost:5279/lbryapi',
|
||||
webUiUri: 'http://localhost:5279',
|
||||
peerListTimeout: 6000,
|
||||
pendingPublishTimeout: 20 * 60 * 1000,
|
||||
colors: {
|
||||
primary: '#155B4A'
|
||||
},
|
||||
|
@ -251,18 +339,49 @@ lbry.getFileInfoWhenListed = function(name, callback, timeoutCallback, tryNum=0)
|
|||
}, () => scheduleNextCheckOrTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes a file. The optional fileListedCallback is called when the file becomes available in
|
||||
* lbry.file_list() during the publish process.
|
||||
*
|
||||
* This currently includes a work-around to cache the file in local storage so that the pending
|
||||
* publish can appear in the UI immediately.
|
||||
*/
|
||||
lbry.publish = function(params, fileListedCallback, publishedCallback, errorCallback) {
|
||||
// Publishes a file.
|
||||
// The optional fileListedCallback is called when the file becomes available in
|
||||
// lbry.getFilesInfo() during the publish process.
|
||||
lbry.call('publish', params, (result) => {
|
||||
if (returnedPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use ES6 named arguments instead of directly passing param dict?
|
||||
lbry.call('publish', params, publishedCallback, errorCallback);
|
||||
if (fileListedCallback) {
|
||||
lbry.getFileInfoWhenListed(params.name, function(fileInfo) {
|
||||
fileListedCallback(fileInfo);
|
||||
});
|
||||
}
|
||||
clearTimeout(returnPendingTimeout);
|
||||
publishedCallback(result);
|
||||
}, (err) => {
|
||||
if (returnedPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(returnPendingTimeout);
|
||||
errorCallback(err);
|
||||
});
|
||||
|
||||
let returnedPending = false;
|
||||
// Give a short grace period in case publish() returns right away or (more likely) gives an error
|
||||
const returnPendingTimeout = setTimeout(() => {
|
||||
returnedPending = true;
|
||||
|
||||
if (publishedCallback) {
|
||||
savePendingPublish(params.name);
|
||||
publishedCallback(true);
|
||||
}
|
||||
|
||||
if (fileListedCallback) {
|
||||
savePendingPublish(params.name);
|
||||
fileListedCallback(true);
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
//lbry.getFileInfoWhenListed(params.name, function(fileInfo) {
|
||||
// fileListedCallback(fileInfo);
|
||||
//});
|
||||
}
|
||||
|
||||
lbry.getVersionInfo = function(callback) {
|
||||
|
@ -503,6 +622,60 @@ lbry.showMenuIfNeeded = function() {
|
|||
sessionStorage.setItem('menuShown', chosenMenu);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrappers for API methods to simulate missing or future behavior. Unlike the old-style stubs,
|
||||
* these are designed to be transparent wrappers around the corresponding API methods.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns results from the file_list API method, plus dummy entries for pending publishes.
|
||||
* (If a real publish with the same name is found, the pending publish will be ignored and removed.)
|
||||
*/
|
||||
lbry.file_list = function(params={}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const {name, outpoint} = params;
|
||||
|
||||
/**
|
||||
* If we're searching by outpoint, check first to see if there's a matching pending publish.
|
||||
* Pending publishes use their own faux outpoints that are always unique, so we don't need
|
||||
* to check if there's a real file.
|
||||
*/
|
||||
if (outpoint !== undefined) {
|
||||
const pendingPublish = getPendingPublish({outpoint});
|
||||
if (pendingPublish) {
|
||||
resolve([pendingPublishToDummyFileInfo(pendingPublish)]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lbry.call('file_list', params, (fileInfos) => {
|
||||
// Remove any pending publications that are now listed in the file manager
|
||||
|
||||
const pendingPublishes = getPendingPublishes();
|
||||
for (let {name: itemName} of fileInfos) {
|
||||
if (pendingPublishes.find(() => name == itemName)) {
|
||||
removePendingPublish({name: name});
|
||||
}
|
||||
}
|
||||
const dummyFileInfos = getPendingPublishes().map(pendingPublishToDummyFileInfo);
|
||||
resolve([...fileInfos, ...dummyFileInfos]);
|
||||
}, reject, reject);
|
||||
});
|
||||
}
|
||||
|
||||
lbry.claim_list_mine = function(params={}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
lbry.call('claim_list_mine', params, (claims) => {
|
||||
// Filter out pending publishes when the name is already in the file manager
|
||||
const dummyClaims = getPendingPublishes().filter(
|
||||
(pub) => !claims.find(({name}) => name == pub.name)
|
||||
).map(pendingPublishToDummyClaim);
|
||||
|
||||
resolve([...claims, ...dummyClaims]);
|
||||
}, reject, reject);
|
||||
});
|
||||
}
|
||||
|
||||
lbry = new Proxy(lbry, {
|
||||
get: function(target, name) {
|
||||
if (name in target) {
|
||||
|
|
15
ui/js/utils.js
Normal file
15
ui/js/utils.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value
|
||||
* is not set yet.
|
||||
*/
|
||||
export function getLocal(key) {
|
||||
const itemRaw = localStorage.getItem(key);
|
||||
return itemRaw === null ? undefined : JSON.parse(itemRaw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin wrapper around localStorage.setItem(). Converts value to JSON.
|
||||
*/
|
||||
export function setLocal(key, value) {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
}
|
Loading…
Add table
Reference in a new issue