lbry-desktop/ui/redux/actions/content.js

311 lines
9.6 KiB
JavaScript

// @flow
import * as SETTINGS from 'constants/settings';
import * as NOTIFICATION_TYPES from 'constants/subscriptions';
import { PAGE_SIZE } from 'constants/claim';
import * as MODALS from 'constants/modal_types';
// @if TARGET='app'
import { ipcRenderer } from 'electron';
// @endif
import { doOpenModal } from 'redux/actions/app';
import { push } from 'connected-react-router';
import { setSubscriptionLatest, doUpdateUnreadSubscriptions } from 'redux/actions/subscriptions';
import { makeSelectUnreadByChannel } from 'redux/selectors/subscriptions';
import {
ACTIONS,
MATURE_TAGS,
Lbry,
Lbryapi,
makeSelectFileInfoForUri,
selectFileInfosByOutpoint,
makeSelectChannelForClaimUri,
parseURI,
doPurchaseUri,
makeSelectUriIsStreamable,
selectDownloadingByOutpoint,
makeSelectClaimForUri,
} from 'lbry-redux';
import { makeSelectCostInfoForUri } from 'lbryinc';
import { makeSelectClientSetting, selectosNotificationsEnabled, selectDaemonSettings } from 'redux/selectors/settings';
import { formatLbryUrlForWeb } from 'util/url';
const DOWNLOAD_POLL_INTERVAL = 250;
export function doUpdateLoadStatus(uri: string, outpoint: string) {
// Updates the loading status for a uri as it's downloading
// Calls file_list and checks the written_bytes value to see if the number has increased
// Not needed on web as users aren't actually downloading the file
// @if TARGET='app'
return (dispatch: Dispatch, getState: GetState) => {
const setNextStatusUpdate = () =>
setTimeout(() => {
// We need to check if outpoint still exists first because user are able to delete file (outpoint) while downloading.
// If a file is already deleted, no point to still try update load status
const byOutpoint = selectFileInfosByOutpoint(getState());
if (byOutpoint[outpoint]) {
dispatch(doUpdateLoadStatus(uri, outpoint));
}
}, DOWNLOAD_POLL_INTERVAL);
Lbry.file_list({
outpoint,
full_status: true,
page: 1,
page_size: 1,
}).then(result => {
const { items: fileInfos } = result;
const fileInfo = fileInfos[0];
if (!fileInfo || fileInfo.written_bytes === 0) {
// download hasn't started yet
setNextStatusUpdate();
} else if (fileInfo.completed) {
const state = getState();
// TODO this isn't going to get called if they reload the client before
// the download finished
dispatch({
type: ACTIONS.DOWNLOADING_COMPLETED,
data: {
uri,
outpoint,
fileInfo,
},
});
const channelUri = makeSelectChannelForClaimUri(uri, true)(state);
const { channelName } = parseURI(channelUri);
const claimName = '@' + channelName;
const unreadForChannel = makeSelectUnreadByChannel(channelUri)(state);
if (unreadForChannel && unreadForChannel.type === NOTIFICATION_TYPES.DOWNLOADING) {
const count = unreadForChannel.uris.length;
if (selectosNotificationsEnabled(state)) {
const notif = new window.Notification(claimName, {
body: `Posted ${fileInfo.metadata.title}${
count > 1 && count < 10 ? ` and ${count - 1} other new items` : ''
}${count > 9 ? ' and 9+ other new items' : ''}`,
silent: false,
});
notif.onclick = () => {
dispatch(push(formatLbryUrlForWeb(uri)));
};
}
dispatch(doUpdateUnreadSubscriptions(channelUri, null, NOTIFICATION_TYPES.DOWNLOADED));
} else {
// If notifications are disabled(false) just return
if (!selectosNotificationsEnabled(getState()) || !fileInfo.written_bytes) return;
const notif = new window.Notification(__('LBRY Download Complete'), {
body: fileInfo.metadata.title,
silent: false,
});
// @if TARGET='app'
notif.onclick = () => {
ipcRenderer.send('focusWindow', 'main');
};
// @ENDIF
}
} else {
// ready to play
const { total_bytes: totalBytes, written_bytes: writtenBytes } = fileInfo;
const progress = (writtenBytes / totalBytes) * 100;
dispatch({
type: ACTIONS.DOWNLOADING_PROGRESSED,
data: {
uri,
outpoint,
fileInfo,
progress,
},
});
setNextStatusUpdate();
}
});
};
// @endif
}
export function doSetPlayingUri(uri: ?string) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.SET_PLAYING_URI,
data: { uri },
});
};
}
export function doFetchClaimsByChannel(uri: string, page: number = 1, pageSize: number = PAGE_SIZE) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const showMature = makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state);
const params = {
channel: uri,
page,
page_size: pageSize,
valid_channel_signature: true,
order_by: ['release_time'],
};
if (!showMature) {
params['not_tags'] = MATURE_TAGS;
}
dispatch({
type: ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED,
data: { uri, page },
});
Lbry.claim_search(params).then(result => {
const { items: claims, page: returnedPage, total_items: claimsInChannel, total_pages: pageTotal } = result;
if (claims && claims.length) {
if (page === 1) {
const latest = claims[0];
dispatch(
setSubscriptionLatest(
{
channelName: latest.signing_channel.name,
uri: latest.signing_channel.permanent_url,
},
latest.permanent_url
)
);
}
}
dispatch({
type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED,
data: {
uri,
claimsInChannel,
claims: claims || [],
page: returnedPage || undefined,
totalPages: pageTotal,
},
});
});
};
}
export function doPurchaseUriWrapper(uri: string, cost: number, saveFile: boolean, cb: ?() => void) {
return (dispatch: Dispatch, getState: () => any) => {
function onSuccess(fileInfo) {
if (saveFile) {
dispatch(doUpdateLoadStatus(uri, fileInfo.outpoint));
}
if (cb) {
cb();
}
}
dispatch(doPurchaseUri(uri, { costInfo: cost }, saveFile, onSuccess));
};
}
export function doPlayUri(
uri: string,
skipCostCheck: boolean = false,
saveFileOverride: boolean = false,
cb?: () => void
) {
return (dispatch: Dispatch, getState: () => any) => {
const state = getState();
const fileInfo = makeSelectFileInfoForUri(uri)(state);
const uriIsStreamable = makeSelectUriIsStreamable(uri)(state);
const downloadingByOutpoint = selectDownloadingByOutpoint(state);
const alreadyDownloaded = fileInfo && (fileInfo.completed || (fileInfo.blobs_remaining === 0 && uriIsStreamable));
const alreadyDownloading = fileInfo && !!downloadingByOutpoint[fileInfo.outpoint];
if (alreadyDownloading || alreadyDownloaded) {
return;
}
const daemonSettings = selectDaemonSettings(state);
const costInfo = makeSelectCostInfoForUri(uri)(state);
const cost = (costInfo && Number(costInfo.cost)) || 0;
const saveFile = !uriIsStreamable ? true : daemonSettings.save_files || saveFileOverride || cost > 0;
const instantPurchaseEnabled = makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED)(state);
const instantPurchaseMax = makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_MAX)(state);
function beginGetFile() {
dispatch(doPurchaseUriWrapper(uri, cost, saveFile, cb));
}
function attemptPlay(instantPurchaseMax = null) {
// If you have a file_list entry, you have already purchased the file
if (!fileInfo && (!instantPurchaseMax || !instantPurchaseEnabled || cost > instantPurchaseMax)) {
dispatch(doOpenModal(MODALS.AFFIRM_PURCHASE, { uri }));
} else {
beginGetFile();
}
}
if (fileInfo && saveFile && (!fileInfo.download_path || !fileInfo.written_bytes)) {
beginGetFile();
return;
}
if (cost === 0 || skipCostCheck) {
beginGetFile();
return;
}
if (instantPurchaseEnabled || instantPurchaseMax.currency === 'LBC') {
attemptPlay(instantPurchaseMax.amount);
} else {
// Need to convert currency of instant purchase maximum before trying to play
Lbryapi.getExchangeRates().then(({ LBC_USD }) => {
attemptPlay(instantPurchaseMax.amount / LBC_USD);
});
}
};
}
export function savePosition(uri: string, position: number) {
return (dispatch: Dispatch, getState: () => any) => {
const state = getState();
const claim = makeSelectClaimForUri(uri)(state);
const { claim_id: claimId, txid, nout } = claim;
const outpoint = `${txid}:${nout}`;
dispatch({
type: ACTIONS.SET_CONTENT_POSITION,
data: { claimId, outpoint, position },
});
};
}
export function doSetContentHistoryItem(uri: string) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.SET_CONTENT_LAST_VIEWED,
data: { uri, lastViewed: Date.now() },
});
};
}
export function doClearContentHistoryUri(uri: string) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.CLEAR_CONTENT_HISTORY_URI,
data: { uri },
});
};
}
export function doClearContentHistoryAll() {
return (dispatch: Dispatch) => {
dispatch({ type: ACTIONS.CLEAR_CONTENT_HISTORY_ALL });
};
}
export function doSetHistoryPage(page: string) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.SET_CONTENT_HISTORY_PAGE,
data: { page },
});
};
}