2018-07-31 14:36:20 +02:00
|
|
|
// @flow
|
2018-10-19 22:38:07 +02:00
|
|
|
import * as NOTIFICATION_TYPES from 'constants/subscriptions';
|
2018-11-03 03:17:55 +01:00
|
|
|
import { PAGE_SIZE } from 'constants/claim';
|
2018-10-29 18:23:53 +01:00
|
|
|
import * as MODALS from 'constants/modal_types';
|
2019-03-05 05:46:57 +01:00
|
|
|
// @if TARGET='app'
|
2017-12-28 00:48:11 +01:00
|
|
|
import { ipcRenderer } from 'electron';
|
2019-03-05 05:46:57 +01:00
|
|
|
// @endif
|
2018-10-29 18:23:53 +01:00
|
|
|
import { doOpenModal } from 'redux/actions/app';
|
2019-04-04 23:05:23 +02:00
|
|
|
import { push } from 'connected-react-router';
|
2018-10-19 22:38:07 +02:00
|
|
|
import { setSubscriptionLatest, doUpdateUnreadSubscriptions } from 'redux/actions/subscriptions';
|
|
|
|
import { makeSelectUnreadByChannel } from 'redux/selectors/subscriptions';
|
2017-04-28 17:14:44 +02:00
|
|
|
import {
|
2018-04-18 06:03:01 +02:00
|
|
|
ACTIONS,
|
|
|
|
SETTINGS,
|
|
|
|
Lbry,
|
|
|
|
Lbryapi,
|
2017-09-08 05:15:05 +02:00
|
|
|
makeSelectFileInfoForUri,
|
2018-10-15 13:27:56 +02:00
|
|
|
selectFileInfosByOutpoint,
|
2018-10-19 22:38:07 +02:00
|
|
|
makeSelectChannelForClaimUri,
|
|
|
|
parseURI,
|
2019-08-02 08:28:14 +02:00
|
|
|
doPurchaseUri,
|
|
|
|
makeSelectUriIsStreamable,
|
2019-08-02 23:03:26 +02:00
|
|
|
selectDownloadingByOutpoint,
|
2019-08-13 07:35:13 +02:00
|
|
|
makeSelectClaimForUri,
|
2018-04-18 06:03:01 +02:00
|
|
|
} from 'lbry-redux';
|
2019-04-24 16:02:08 +02:00
|
|
|
import { makeSelectCostInfoForUri } from 'lbryinc';
|
2019-08-02 23:03:26 +02:00
|
|
|
import { makeSelectClientSetting, selectosNotificationsEnabled, selectDaemonSettings } from 'redux/selectors/settings';
|
2019-03-28 17:53:13 +01:00
|
|
|
import { formatLbryUriForWeb } from 'util/uri';
|
2017-06-24 18:48:01 +02:00
|
|
|
|
2017-08-30 18:43:35 +02:00
|
|
|
const DOWNLOAD_POLL_INTERVAL = 250;
|
|
|
|
|
2018-10-15 13:27:56 +02:00
|
|
|
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) => {
|
2018-10-15 13:27:56 +02:00
|
|
|
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
|
2018-10-15 13:27:56 +02:00
|
|
|
const byOutpoint = selectFileInfosByOutpoint(getState());
|
|
|
|
if (byOutpoint[outpoint]) {
|
|
|
|
dispatch(doUpdateLoadStatus(uri, outpoint));
|
|
|
|
}
|
|
|
|
}, DOWNLOAD_POLL_INTERVAL);
|
2019-03-13 06:59:07 +01:00
|
|
|
|
2017-12-21 18:32:51 +01:00
|
|
|
Lbry.file_list({
|
|
|
|
outpoint,
|
|
|
|
full_status: true,
|
|
|
|
}).then(([fileInfo]) => {
|
|
|
|
if (!fileInfo || fileInfo.written_bytes === 0) {
|
|
|
|
// download hasn't started yet
|
2018-10-15 13:27:56 +02:00
|
|
|
setNextStatusUpdate();
|
2017-12-21 18:32:51 +01:00
|
|
|
} else if (fileInfo.completed) {
|
2018-08-10 16:51:51 +02:00
|
|
|
const state = getState();
|
2017-12-21 18:32:51 +01:00
|
|
|
// 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,
|
|
|
|
},
|
|
|
|
});
|
2017-06-24 10:57:37 +02:00
|
|
|
|
2018-10-19 22:38:07 +02:00
|
|
|
const channelUri = makeSelectChannelForClaimUri(uri, true)(state);
|
2019-08-30 01:18:06 +02:00
|
|
|
const { channelName } = parseURI(channelUri);
|
|
|
|
const claimName = '@' + channelName;
|
2018-10-19 22:38:07 +02:00
|
|
|
|
|
|
|
const unreadForChannel = makeSelectUnreadByChannel(channelUri)(state);
|
2019-01-15 02:02:11 +01:00
|
|
|
if (unreadForChannel && unreadForChannel.type === NOTIFICATION_TYPES.DOWNLOADING) {
|
2018-10-19 22:38:07 +02:00
|
|
|
const count = unreadForChannel.uris.length;
|
2018-05-07 06:50:55 +02:00
|
|
|
|
2018-08-14 15:27:29 +02:00
|
|
|
if (selectosNotificationsEnabled(state)) {
|
2019-08-30 01:18:06 +02:00
|
|
|
const notif = new window.Notification(claimName, {
|
2018-07-31 02:13:57 +02:00
|
|
|
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 = () => {
|
2019-04-04 23:05:23 +02:00
|
|
|
dispatch(push(formatLbryUriForWeb(uri)));
|
2018-07-31 02:13:57 +02:00
|
|
|
};
|
|
|
|
}
|
2018-10-19 22:38:07 +02:00
|
|
|
|
|
|
|
dispatch(doUpdateUnreadSubscriptions(channelUri, null, NOTIFICATION_TYPES.DOWNLOADED));
|
2018-03-26 09:31:52 +02:00
|
|
|
} else {
|
2018-07-31 02:13:57 +02:00
|
|
|
// If notifications are disabled(false) just return
|
2019-08-02 08:28:14 +02:00
|
|
|
if (!selectosNotificationsEnabled(getState()) || !fileInfo.written_bytes) return;
|
2018-10-19 22:38:07 +02:00
|
|
|
|
2019-08-13 07:35:13 +02:00
|
|
|
const notif = new window.Notification(__('LBRY Download Complete'), {
|
2018-03-26 09:31:52 +02:00
|
|
|
body: fileInfo.metadata.title,
|
|
|
|
silent: false,
|
|
|
|
});
|
2019-03-05 05:46:57 +01:00
|
|
|
|
|
|
|
// @if TARGET='app'
|
2018-03-26 09:31:52 +02:00
|
|
|
notif.onclick = () => {
|
|
|
|
ipcRenderer.send('focusWindow', 'main');
|
|
|
|
};
|
2019-03-05 05:46:57 +01:00
|
|
|
// @ENDIF
|
2018-03-26 09:31:52 +02:00
|
|
|
}
|
2017-12-21 18:32:51 +01:00
|
|
|
} else {
|
|
|
|
// ready to play
|
|
|
|
const { total_bytes: totalBytes, written_bytes: writtenBytes } = fileInfo;
|
2018-07-12 21:37:01 +02:00
|
|
|
const progress = (writtenBytes / totalBytes) * 100;
|
2017-06-06 23:19:12 +02:00
|
|
|
|
2017-12-21 18:32:51 +01:00
|
|
|
dispatch({
|
|
|
|
type: ACTIONS.DOWNLOADING_PROGRESSED,
|
|
|
|
data: {
|
|
|
|
uri,
|
|
|
|
outpoint,
|
|
|
|
fileInfo,
|
|
|
|
progress,
|
|
|
|
},
|
|
|
|
});
|
2017-06-24 10:57:37 +02:00
|
|
|
|
2018-10-15 13:27:56 +02:00
|
|
|
setNextStatusUpdate();
|
2017-12-21 18:32:51 +01:00
|
|
|
}
|
|
|
|
});
|
2017-06-06 23:19:12 +02:00
|
|
|
};
|
2019-03-13 06:59:07 +01:00
|
|
|
// @endif
|
2017-04-26 19:08:26 +02:00
|
|
|
}
|
|
|
|
|
2019-03-18 06:09:50 +01:00
|
|
|
export function doSetPlayingUri(uri: ?string) {
|
|
|
|
return (dispatch: Dispatch) => {
|
2017-12-21 18:32:51 +01:00
|
|
|
dispatch({
|
|
|
|
type: ACTIONS.SET_PLAYING_URI,
|
|
|
|
data: { uri },
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
2017-04-26 19:08:26 +02:00
|
|
|
|
2019-05-07 23:38:29 +02:00
|
|
|
export function doFetchClaimsByChannel(uri: string, page: number = 1, pageSize: number = PAGE_SIZE) {
|
2019-03-18 06:09:50 +01:00
|
|
|
return (dispatch: Dispatch) => {
|
2017-05-13 00:50:51 +02:00
|
|
|
dispatch({
|
2017-12-21 18:32:51 +01:00
|
|
|
type: ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED,
|
2017-07-17 08:06:04 +02:00
|
|
|
data: { uri, page },
|
2017-06-06 23:19:12 +02:00
|
|
|
});
|
2017-05-13 00:50:51 +02:00
|
|
|
|
2019-06-11 20:15:36 +02:00
|
|
|
Lbry.claim_search({
|
|
|
|
channel: uri,
|
|
|
|
page,
|
|
|
|
page_size: pageSize,
|
2019-06-24 21:40:53 +02:00
|
|
|
valid_channel_signature: true,
|
2019-06-11 20:15:36 +02:00
|
|
|
order_by: ['release_time'],
|
|
|
|
}).then(result => {
|
2019-04-24 16:02:08 +02:00
|
|
|
const { items: claimsInChannel, page: returnedPage } = result;
|
2018-11-03 03:17:55 +01:00
|
|
|
|
2019-01-11 17:34:36 +01:00
|
|
|
if (claimsInChannel && claimsInChannel.length) {
|
|
|
|
if (page === 1) {
|
2018-11-03 03:17:55 +01:00
|
|
|
const latest = claimsInChannel[0];
|
|
|
|
dispatch(
|
|
|
|
setSubscriptionLatest(
|
|
|
|
{
|
2019-08-30 01:18:06 +02:00
|
|
|
channelName: latest.signing_channel.name,
|
|
|
|
uri: latest.signing_channel.permanent_url,
|
2018-11-03 03:17:55 +01:00
|
|
|
},
|
2019-08-30 01:18:06 +02:00
|
|
|
latest.permanent_url
|
2018-11-03 03:17:55 +01:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2018-03-06 01:28:11 +01:00
|
|
|
}
|
2019-01-11 17:34:36 +01:00
|
|
|
|
|
|
|
dispatch({
|
|
|
|
type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED,
|
|
|
|
data: {
|
|
|
|
uri,
|
|
|
|
claims: claimsInChannel || [],
|
|
|
|
page: returnedPage || undefined,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
2017-06-06 23:19:12 +02:00
|
|
|
};
|
2017-05-19 01:14:26 +02:00
|
|
|
}
|
|
|
|
|
2019-08-02 23:03:26 +02:00
|
|
|
export function doPurchaseUriWrapper(uri: string, cost: number, saveFile: boolean) {
|
2019-08-02 08:28:14 +02:00
|
|
|
return (dispatch: Dispatch, getState: () => any) => {
|
|
|
|
function onSuccess(fileInfo) {
|
|
|
|
dispatch(doUpdateLoadStatus(uri, fileInfo.outpoint));
|
|
|
|
}
|
|
|
|
|
2019-08-02 23:03:26 +02:00
|
|
|
// Only pass the sucess callback if we are saving the file, otherwise we don't show the download percentage
|
|
|
|
const successCallBack = saveFile ? onSuccess : undefined;
|
|
|
|
dispatch(doPurchaseUri(uri, { costInfo: cost }, saveFile, successCallBack));
|
2019-08-02 08:28:14 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-08-02 23:03:26 +02:00
|
|
|
export function doPlayUri(uri: string, skipCostCheck: boolean = false, saveFileOverride: boolean = false) {
|
2019-08-02 08:28:14 +02:00
|
|
|
return (dispatch: Dispatch, getState: () => any) => {
|
|
|
|
const state = getState();
|
|
|
|
const fileInfo = makeSelectFileInfoForUri(uri)(state);
|
2019-08-02 23:03:26 +02:00
|
|
|
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);
|
2019-08-30 01:18:06 +02:00
|
|
|
const cost = (costInfo && Number(costInfo.cost)) || 0;
|
2019-08-02 23:03:26 +02:00
|
|
|
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);
|
2019-08-02 08:28:14 +02:00
|
|
|
|
2019-08-02 23:03:26 +02:00
|
|
|
function beginGetFile() {
|
|
|
|
dispatch(doPurchaseUriWrapper(uri, cost, saveFile));
|
|
|
|
}
|
|
|
|
|
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
|
2019-08-29 04:07:41 +02:00
|
|
|
if (!fileInfo && (!instantPurchaseMax || !instantPurchaseEnabled || cost > instantPurchaseMax)) {
|
2019-08-02 23:03:26 +02:00
|
|
|
dispatch(doOpenModal(MODALS.AFFIRM_PURCHASE, { uri }));
|
|
|
|
} 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
|
|
|
|
2019-08-15 00:27:55 +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;
|
|
|
|
}
|
|
|
|
|
2019-08-02 23:03:26 +02:00
|
|
|
if (instantPurchaseEnabled || instantPurchaseMax.currency === 'LBC') {
|
2019-08-29 06:11:10 +02:00
|
|
|
attemptPlay(instantPurchaseMax.amount);
|
2019-08-02 08:28:14 +02:00
|
|
|
} else {
|
2019-08-02 23:03:26 +02:00
|
|
|
// Need to convert currency of instant purchase maximum before trying to play
|
|
|
|
Lbryapi.getExchangeRates().then(({ LBC_USD }) => {
|
|
|
|
attemptPlay(instantPurchaseMax.amount / LBC_USD);
|
|
|
|
});
|
2019-08-02 08:28:14 +02:00
|
|
|
}
|
2017-09-18 04:08:43 +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}`;
|
|
|
|
|
2018-07-31 14:36:20 +02:00
|
|
|
dispatch({
|
|
|
|
type: ACTIONS.SET_CONTENT_POSITION,
|
|
|
|
data: { claimId, outpoint, position },
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
2018-07-31 20:07:45 +02:00
|
|
|
|
|
|
|
export function doSetContentHistoryItem(uri: string) {
|
2019-03-18 06:09:50 +01:00
|
|
|
return (dispatch: Dispatch) => {
|
2018-07-31 20:07:45 +02:00
|
|
|
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) => {
|
2018-07-31 20:07:45 +02:00
|
|
|
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) => {
|
2018-07-31 20:07:45 +02:00
|
|
|
dispatch({ type: ACTIONS.CLEAR_CONTENT_HISTORY_ALL });
|
|
|
|
};
|
|
|
|
}
|
2018-08-01 16:06:43 +02:00
|
|
|
|
2019-03-18 06:09:50 +01:00
|
|
|
export function doSetHistoryPage(page: string) {
|
|
|
|
return (dispatch: Dispatch) => {
|
2018-08-01 16:06:43 +02:00
|
|
|
dispatch({
|
|
|
|
type: ACTIONS.SET_CONTENT_HISTORY_PAGE,
|
|
|
|
data: { page },
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|