From b44be392523fd5baabba1821dc28f48000dd0fa1 Mon Sep 17 00:00:00 2001 From: zeppi Date: Tue, 5 Oct 2021 16:57:47 -0400 Subject: [PATCH] move file actions from lbry-redux --- flow-typed/File.js | 78 ++++++++++++++ ui/component/claimPreview/index.js | 3 +- ui/component/claimPreviewTile/index.js | 2 +- ui/component/sideNavigation/index.js | 4 +- ui/constants/action_types.js | 7 ++ ui/redux/actions/content.js | 8 +- ui/redux/actions/file.js | 144 +++++++++++++++++++++++-- 7 files changed, 231 insertions(+), 15 deletions(-) create mode 100644 flow-typed/File.js diff --git a/flow-typed/File.js b/flow-typed/File.js new file mode 100644 index 000000000..44dc4f398 --- /dev/null +++ b/flow-typed/File.js @@ -0,0 +1,78 @@ +// @flow + +declare type FileListItem = { + metadata: StreamMetadata, + added_on: number, + blobs_completed: number, + blobs_in_stream: number, + blobs_remaining: number, + channel_claim_id: string, + channel_name: string, + claim_id: string, + claim_name: string, + completed: false, + content_fee?: { txid: string }, + purchase_receipt?: { txid: string, amount: string }, + download_directory: string, + download_path: string, + file_name: string, + key: string, + mime_type: string, + nout: number, + outpoint: string, + points_paid: number, + protobuf: string, + reflector_progress: number, + sd_hash: string, + status: string, + stopped: false, + stream_hash: string, + stream_name: string, + streaming_url: string, + suggested_file_name: string, + total_bytes: number, + total_bytes_lower_bound: number, + is_fully_reflected: boolean, + // TODO: sdk plans to change `tx` + // It isn't currently used by the apps + tx: {}, + txid: string, + uploading_to_reflector: boolean, + written_bytes: number, +}; + +declare type FileState = { + failedPurchaseUris: Array, + purchasedUris: Array, +}; + +declare type PurchaseUriCompleted = { + type: ACTIONS.PURCHASE_URI_COMPLETED, + data: { + uri: string, + streamingUrl: string, + }, +}; + +declare type PurchaseUriFailed = { + type: ACTIONS.PURCHASE_URI_FAILED, + data: { + uri: string, + error: any, + }, +}; + +declare type PurchaseUriStarted = { + type: ACTIONS.PURCHASE_URI_STARTED, + data: { + uri: string, + streamingUrl: string, + }, +}; + +declare type DeletePurchasedUri = { + type: ACTIONS.CLEAR_PURCHASED_URI_SUCCESS, + data: { + uri: string, + }, +}; diff --git a/ui/component/claimPreview/index.js b/ui/component/claimPreview/index.js index 832004474..2dc96541d 100644 --- a/ui/component/claimPreview/index.js +++ b/ui/component/claimPreview/index.js @@ -6,7 +6,6 @@ import { makeSelectClaimIsMine, makeSelectClaimIsPending, makeSelectClaimIsNsfw, - doFileGet, makeSelectReflectingClaimForUri, makeSelectClaimWasPurchased, makeSelectStreamingUrlForUri, @@ -25,7 +24,7 @@ import { selectShowMatureContent } from 'redux/selectors/settings'; import { makeSelectHasVisitedUri } from 'redux/selectors/content'; import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; import { selectModerationBlockList } from 'redux/selectors/comments'; - +import { doFileGet } from 'redux/actions/file'; import ClaimPreview from './view'; import formatMediaDuration from 'util/formatMediaDuration'; diff --git a/ui/component/claimPreviewTile/index.js b/ui/component/claimPreviewTile/index.js index 40b3355c0..59d506718 100644 --- a/ui/component/claimPreviewTile/index.js +++ b/ui/component/claimPreviewTile/index.js @@ -5,7 +5,6 @@ import { makeSelectIsUriResolving, makeSelectThumbnailForUri, makeSelectTitleForUri, - doFileGet, makeSelectChannelForClaimUri, makeSelectClaimIsNsfw, makeSelectClaimIsStreamPlaceholder, @@ -14,6 +13,7 @@ import { import { selectMutedChannels } from 'redux/selectors/blocked'; import { makeSelectViewCountForUri, selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc'; import { makeSelectIsActiveLivestream } from 'redux/selectors/livestream'; +import { doFileGet } from 'redux/actions/file'; import { selectShowMatureContent } from 'redux/selectors/settings'; import ClaimPreviewTile from './view'; import formatMediaDuration from 'util/formatMediaDuration'; diff --git a/ui/component/sideNavigation/index.js b/ui/component/sideNavigation/index.js index 6eda01389..40a13ccc8 100644 --- a/ui/component/sideNavigation/index.js +++ b/ui/component/sideNavigation/index.js @@ -1,10 +1,12 @@ import { connect } from 'react-redux'; import { selectSubscriptions } from 'redux/selectors/subscriptions'; -import { selectPurchaseUriSuccess, doClearPurchasedUriSuccess } from 'lbry-redux'; +import { selectPurchaseUriSuccess } from 'lbry-redux'; import { selectFollowedTags } from 'redux/selectors/tags'; import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user'; import { selectHomepageData, selectLanguage } from 'redux/selectors/settings'; import { doSignOut } from 'redux/actions/app'; +import { doClearPurchasedUriSuccess } from 'redux/actions/file'; + import { selectUnseenNotificationCount } from 'redux/selectors/notifications'; import SideNavigation from './view'; diff --git a/ui/constants/action_types.js b/ui/constants/action_types.js index 7345e773f..4460028ed 100644 --- a/ui/constants/action_types.js +++ b/ui/constants/action_types.js @@ -120,6 +120,13 @@ export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED'; export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED'; export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED'; export const FILE_DELETE = 'FILE_DELETE'; +export const FETCH_FILE_INFO_FAILED = 'FETCH_FILE_INFO_FAILED'; +export const DOWNLOADING_CANCELED = 'DOWNLOADING_CANCELED'; +export const SET_FILE_LIST_SORT = 'SET_FILE_LIST_SORT'; +export const PURCHASE_URI_STARTED = 'PURCHASE_URI_STARTED'; +export const PURCHASE_URI_COMPLETED = 'PURCHASE_URI_COMPLETED'; +export const PURCHASE_URI_FAILED = 'PURCHASE_URI_FAILED'; +export const CLEAR_PURCHASED_URI_SUCCESS = 'CLEAR_PURCHASED_URI_SUCCESS'; // Search export const SEARCH_START = 'SEARCH_START'; diff --git a/ui/redux/actions/content.js b/ui/redux/actions/content.js index 7e76e3962..1053b617a 100644 --- a/ui/redux/actions/content.js +++ b/ui/redux/actions/content.js @@ -10,7 +10,6 @@ import { SETTINGS, makeSelectFileInfoForUri, selectFileInfosByOutpoint, - doPurchaseUri, makeSelectUriIsStreamable, selectDownloadingByOutpoint, makeSelectClaimForUri, @@ -19,6 +18,7 @@ import { doToast, makeSelectUrlsForCollectionId, } from 'lbry-redux'; +import { doPurchaseUri } from 'redux/actions/file'; import { makeSelectCostInfoForUri, Lbryio } from 'lbryinc'; import { makeSelectClientSetting, selectosNotificationsEnabled, selectDaemonSettings } from 'redux/selectors/settings'; @@ -117,8 +117,8 @@ export function doSetPlayingUri({ uri: ?string, source?: string, commentId?: string, - pathname: string, - collectionId: string, + pathname?: string, + collectionId?: string, }) { return (dispatch: Dispatch) => { dispatch({ @@ -140,7 +140,7 @@ export function doPurchaseUriWrapper(uri: string, cost: number, saveFile: boolea } } - dispatch(doPurchaseUri(uri, { costInfo: cost }, saveFile, onSuccess)); + dispatch(doPurchaseUri(uri, { cost }, saveFile, onSuccess)); }; } diff --git a/ui/redux/actions/file.js b/ui/redux/actions/file.js index 447fd4f20..73adf0bd4 100644 --- a/ui/redux/actions/file.js +++ b/ui/redux/actions/file.js @@ -1,3 +1,4 @@ +// @flow import * as ACTIONS from 'constants/action_types'; // @if TARGET='app' import { shell } from 'electron'; @@ -7,22 +8,28 @@ import { batchActions, doAbandonClaim, makeSelectFileInfoForUri, + selectDownloadingByOutpoint, + makeSelectStreamingUrlForUri, makeSelectClaimForUri, + selectBalance, ABANDON_STATES, } from 'lbry-redux'; import { doHideModal } from 'redux/actions/app'; import { goBack } from 'connected-react-router'; import { doSetPlayingUri } from 'redux/actions/content'; import { selectPlayingUri } from 'redux/selectors/content'; +import { doToast } from 'redux/actions/notifications'; +type Dispatch = (action: any) => any; +type GetState = () => { file: FileState }; -export function doOpenFileInFolder(path) { +export function doOpenFileInFolder(path: string) { return () => { shell.showItemInFolder(path); }; } -export function doOpenFileInShell(path) { - return (dispatch) => { +export function doOpenFileInShell(path: string) { + return (dispatch: Dispatch) => { const success = shell.openPath(path); if (!success) { dispatch(doOpenFileInFolder(path)); @@ -30,8 +37,8 @@ export function doOpenFileInShell(path) { }; } -export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim, cb) { - return (dispatch) => { +export function doDeleteFile(outpoint: string, deleteFromComputer?: boolean, abandonClaim?: boolean, cb: any) { + return (dispatch: Dispatch) => { if (abandonClaim) { const [txid, nout] = outpoint.split(':'); dispatch(doAbandonClaim(txid, Number(nout), cb)); @@ -53,8 +60,13 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim, cb) { }; } -export function doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim, doGoBack) { - return (dispatch, getState) => { +export function doDeleteFileAndMaybeGoBack( + uri: string, + deleteFromComputer?: boolean, + abandonClaim?: boolean, + doGoBack: (any) => void +) { + return (dispatch: Dispatch, getState: GetState) => { const state = getState(); const playingUri = selectPlayingUri(state); const { outpoint } = makeSelectFileInfoForUri(uri)(state) || ''; @@ -88,3 +100,121 @@ export function doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim dispatch(batchActions(...actions)); }; } + +export function doFileGet(uri: string, saveFile: boolean = true, onSuccess?: (GetResponse) => any) { + return (dispatch: Dispatch, getState: () => any) => { + const state = getState(); + const { nout, txid } = makeSelectClaimForUri(uri)(state); + const outpoint = `${txid}:${nout}`; + + dispatch({ + type: ACTIONS.FETCH_FILE_INFO_STARTED, + data: { + outpoint, + }, + }); + + // set save_file argument to True to save the file (old behaviour) + Lbry.get({ uri, save_file: saveFile }) + .then((streamInfo: GetResponse) => { + const timeout = streamInfo === null || typeof streamInfo !== 'object' || streamInfo.error === 'Timeout'; + if (timeout) { + dispatch({ + type: ACTIONS.FETCH_FILE_INFO_FAILED, + data: { outpoint }, + }); + + dispatch(doToast({ message: `File timeout for uri ${uri}`, isError: true })); + } else { + if (streamInfo.purchase_receipt || streamInfo.content_fee) { + dispatch({ + type: ACTIONS.PURCHASE_URI_COMPLETED, + data: { uri, purchaseReceipt: streamInfo.purchase_receipt || streamInfo.content_fee }, + }); + } + dispatch({ + type: ACTIONS.FETCH_FILE_INFO_COMPLETED, + data: { + fileInfo: streamInfo, + outpoint: outpoint, + }, + }); + + if (onSuccess) { + onSuccess(streamInfo); + } + } + }) + .catch((error) => { + dispatch({ + type: ACTIONS.PURCHASE_URI_FAILED, + data: { uri, error }, + }); + + dispatch({ + type: ACTIONS.FETCH_FILE_INFO_FAILED, + data: { outpoint }, + }); + + dispatch( + doToast({ + message: `Failed to view ${uri}, please try again. If this problem persists, visit https://lbry.com/faq/support for support.`, + isError: true, + }) + ); + }); + }; +} + +export function doPurchaseUri( + uri: string, + costInfo: { cost: number }, + saveFile: boolean = true, + onSuccess?: (GetResponse) => any +) { + return (dispatch: Dispatch, getState: GetState) => { + dispatch({ + type: ACTIONS.PURCHASE_URI_STARTED, + data: { uri }, + }); + + const state = getState(); + const balance = selectBalance(state); + const fileInfo = makeSelectFileInfoForUri(uri)(state); + const downloadingByOutpoint = selectDownloadingByOutpoint(state); + const alreadyDownloading = fileInfo && !!downloadingByOutpoint[fileInfo.outpoint]; + const alreadyStreaming = makeSelectStreamingUrlForUri(uri)(state); + + if (!saveFile && (alreadyDownloading || alreadyStreaming)) { + dispatch({ + type: ACTIONS.PURCHASE_URI_FAILED, + data: { uri, error: `Already fetching uri: ${uri}` }, + }); + + if (onSuccess) { + onSuccess(fileInfo); + } + + return; + } + + const { cost } = costInfo; + if (parseFloat(cost) > balance) { + dispatch({ + type: ACTIONS.PURCHASE_URI_FAILED, + data: { uri, error: 'Insufficient credits' }, + }); + + Promise.resolve(); + return; + } + + dispatch(doFileGet(uri, saveFile, onSuccess)); + }; +} + +export function doClearPurchasedUriSuccess() { + return { + type: ACTIONS.CLEAR_PURCHASED_URI_SUCCESS, + }; +}