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

340 lines
10 KiB
JavaScript
Raw Normal View History

// @flow
import * as ACTIONS from 'constants/action_types';
import * as MODALS from 'constants/modal_types';
2019-03-05 05:46:57 +01:00
// @if TARGET='app'
import { ipcRenderer } from 'electron';
2019-03-05 05:46:57 +01:00
// @endif
import { doOpenModal } from 'redux/actions/app';
import { makeSelectClaimForUri, selectClaimIsMineForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
2017-04-28 17:14:44 +02:00
import {
2017-09-08 05:15:05 +02:00
makeSelectFileInfoForUri,
selectFileInfosByOutpoint,
2019-08-02 08:28:14 +02:00
makeSelectUriIsStreamable,
2019-08-02 23:03:26 +02:00
selectDownloadingByOutpoint,
} from 'redux/selectors/file_info';
import { makeSelectUrlsForCollectionId } from 'redux/selectors/collections';
import { doToast } from 'redux/actions/notifications';
2021-10-05 22:57:47 +02:00
import { doPurchaseUri } from 'redux/actions/file';
import Lbry from 'lbry';
import * as SETTINGS from 'constants/settings';
import { selectCostInfoForUri, Lbryio } from 'lbryinc';
2019-08-02 23:03:26 +02:00
import { makeSelectClientSetting, selectosNotificationsEnabled, selectDaemonSettings } from 'redux/selectors/settings';
const DOWNLOAD_POLL_INTERVAL = 1000;
export function doUpdateLoadStatus(uri: string, outpoint: string) {
2019-03-13 06:59:07 +01:00
// 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'
2019-03-18 06:09:50 +01:00
return (dispatch: Dispatch, getState: GetState) => {
const setNextStatusUpdate = () =>
setTimeout(() => {
2018-10-18 14:38:12 +02:00
// We need to check if outpoint still exists first because user are able to delete file (outpoint) while downloading.
2018-10-18 17:23:08 +02:00
// 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);
2019-03-13 06:59:07 +01:00
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) {
// 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,
},
});
2020-11-06 06:02:21 +01:00
// If notifications are disabled(false) just return
if (!selectosNotificationsEnabled(getState()) || !fileInfo.written_bytes) return;
2018-10-19 22:38:07 +02:00
2020-11-06 06:02:21 +01:00
const notif = new window.Notification(__('LBRY Download Complete'), {
body: fileInfo.metadata.title,
silent: false,
});
2019-03-05 05:46:57 +01:00
2020-11-06 06:02:21 +01:00
// @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;
2017-06-06 23:19:12 +02:00
dispatch({
type: ACTIONS.DOWNLOADING_PROGRESSED,
data: {
uri,
outpoint,
fileInfo,
progress,
},
});
setNextStatusUpdate();
}
});
2017-06-06 23:19:12 +02:00
};
2019-03-13 06:59:07 +01:00
// @endif
}
export function doSetPrimaryUri(uri: ?string) {
2019-03-18 06:09:50 +01:00
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.SET_PRIMARY_URI,
data: { uri },
});
};
}
export function doSetPlayingUri({
uri,
source,
pathname,
commentId,
collectionId,
}: {
uri: ?string,
source?: string,
commentId?: string,
2021-10-05 22:57:47 +02:00
pathname?: string,
collectionId?: string,
}) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.SET_PLAYING_URI,
data: { uri, source, pathname, commentId, collectionId },
});
};
}
2020-05-21 17:38:28 +02:00
export function doPurchaseUriWrapper(uri: string, cost: number, saveFile: boolean, cb: ?(GetResponse) => void) {
2019-08-02 08:28:14 +02:00
return (dispatch: Dispatch, getState: () => any) => {
function onSuccess(fileInfo) {
if (saveFile) {
dispatch(doUpdateLoadStatus(uri, fileInfo.outpoint));
}
if (cb) {
2020-05-21 17:38:28 +02:00
cb(fileInfo);
}
2019-08-02 08:28:14 +02:00
}
2021-10-05 22:57:47 +02:00
dispatch(doPurchaseUri(uri, { cost }, saveFile, onSuccess));
2019-08-02 08:28:14 +02:00
};
}
export function doPlayUri(
uri: string,
skipCostCheck: boolean = false,
saveFileOverride: boolean = false,
cb?: () => void,
hideFailModal: boolean = false
) {
2019-08-02 08:28:14 +02:00
return (dispatch: Dispatch, getState: () => any) => {
const state = getState();
const isMine = selectClaimIsMineForUri(state, uri);
2019-08-02 08:28:14 +02:00
const fileInfo = makeSelectFileInfoForUri(uri)(state);
2019-08-02 23:03:26 +02:00
const uriIsStreamable = makeSelectUriIsStreamable(uri)(state);
const downloadingByOutpoint = selectDownloadingByOutpoint(state);
2020-05-21 17:38:28 +02:00
const claimWasPurchased = makeSelectClaimWasPurchased(uri)(state);
2019-08-02 23:03:26 +02:00
const alreadyDownloaded = fileInfo && (fileInfo.completed || (fileInfo.blobs_remaining === 0 && uriIsStreamable));
const alreadyDownloading = fileInfo && !!downloadingByOutpoint[fileInfo.outpoint];
2020-05-21 17:38:28 +02:00
if (!IS_WEB && (alreadyDownloading || alreadyDownloaded)) {
2019-08-02 23:03:26 +02:00
return;
}
const daemonSettings = selectDaemonSettings(state);
const costInfo = selectCostInfoForUri(state, uri);
const cost = (costInfo && Number(costInfo.cost)) || 0;
2020-05-21 17:38:28 +02:00
const saveFile = !IS_WEB && (!uriIsStreamable ? true : daemonSettings.save_files || saveFileOverride || cost > 0);
2019-08-02 23:03:26 +02:00
const instantPurchaseEnabled = makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED)(state);
const instantPurchaseMax = makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_MAX)(state);
2019-08-02 08:28:14 +02:00
2019-08-02 23:03:26 +02:00
function beginGetFile() {
dispatch(doPurchaseUriWrapper(uri, cost, saveFile, cb));
2019-08-02 23:03:26 +02:00
}
2019-08-29 06:11:10 +02:00
function attemptPlay(instantPurchaseMax = null) {
2019-08-02 23:03:26 +02:00
// If you have a file_list entry, you have already purchased the file
2020-05-21 17:38:28 +02:00
if (
!isMine &&
!fileInfo &&
!claimWasPurchased &&
(!instantPurchaseMax || !instantPurchaseEnabled || cost > instantPurchaseMax)
) {
if (!hideFailModal) dispatch(doOpenModal(MODALS.AFFIRM_PURCHASE, { uri }));
2019-08-02 23:03:26 +02:00
} else {
beginGetFile();
2019-08-02 08:28:14 +02:00
}
2019-08-02 23:03:26 +02:00
}
2019-08-02 08:28:14 +02:00
if (fileInfo && saveFile && (!fileInfo.download_path || !fileInfo.written_bytes)) {
beginGetFile();
return;
}
2019-08-02 23:03:26 +02:00
if (cost === 0 || skipCostCheck) {
beginGetFile();
2019-08-02 08:28:14 +02:00
return;
}
if (instantPurchaseEnabled) {
if (instantPurchaseMax.currency === 'LBC') {
attemptPlay(instantPurchaseMax.amount);
} else {
// Need to convert currency of instant purchase maximum before trying to play
Lbryio.getExchangeRates().then(({ LBC_USD }) => {
attemptPlay(instantPurchaseMax.amount / LBC_USD);
});
}
2019-08-02 08:28:14 +02:00
} else {
attemptPlay();
2019-08-02 08:28:14 +02:00
}
};
}
2019-08-13 07:35:13 +02:00
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 clearPosition(uri: string) {
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.CLEAR_CONTENT_POSITION,
data: { claimId, outpoint },
});
};
}
export function doSetContentHistoryItem(uri: string) {
2019-03-18 06:09:50 +01:00
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.SET_CONTENT_LAST_VIEWED,
data: { uri, lastViewed: Date.now() },
});
};
}
export function doClearContentHistoryUri(uri: string) {
2019-03-18 06:09:50 +01:00
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.CLEAR_CONTENT_HISTORY_URI,
data: { uri },
});
};
}
2018-08-01 16:06:43 +02:00
export function doClearContentHistoryAll() {
2019-03-18 06:09:50 +01:00
return (dispatch: Dispatch) => {
dispatch({ type: ACTIONS.CLEAR_CONTENT_HISTORY_ALL });
};
}
export const doRecommendationUpdate = (claimId: string, urls: Array<string>, id: string, parentId: string) => (
dispatch: Dispatch
) => {
dispatch({
type: ACTIONS.RECOMMENDATION_UPDATED,
data: { claimId, urls, id, parentId },
});
};
export const doRecommendationClicked = (claimId: string, index: number) => (dispatch: Dispatch) => {
if (index !== undefined && index !== null) {
dispatch({
type: ACTIONS.RECOMMENDATION_CLICKED,
data: { claimId, index },
});
}
};
export function doToggleLoopList(collectionId: string, loop: boolean, hideToast: boolean) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.TOGGLE_LOOP_LIST,
data: { collectionId, loop },
});
if (!hideToast) {
dispatch(
doToast({
message: loop ? __('Loop is on.') : __('Loop is off.'),
})
);
}
};
}
export function doToggleShuffleList(currentUri: string, collectionId: string, shuffle: boolean, hideToast: boolean) {
return (dispatch: Dispatch, getState: () => any) => {
if (shuffle) {
const state = getState();
const urls = makeSelectUrlsForCollectionId(collectionId)(state);
let newUrls = urls
.map((item) => ({ item, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ item }) => item);
// the currently playing URI should be first in list or else
// can get in strange position where it might be in the middle or last
// and the shuffled list ends before scrolling through all entries
if (currentUri && currentUri !== '') {
newUrls.splice(newUrls.indexOf(currentUri), 1);
newUrls.splice(0, 0, currentUri);
}
dispatch({
type: ACTIONS.TOGGLE_SHUFFLE_LIST,
data: { collectionId, newUrls },
});
} else {
dispatch({
type: ACTIONS.TOGGLE_SHUFFLE_LIST,
data: { collectionId, newUrls: false },
});
}
if (!hideToast) {
dispatch(
doToast({
message: shuffle ? __('Shuffle is on.') : __('Shuffle is off.'),
})
);
}
};
}