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' ;
2017-12-28 00:48:11 +01:00
import { ipcRenderer } from 'electron' ;
2018-10-29 18:23:53 +01:00
import { doOpenModal } from 'redux/actions/app' ;
2018-03-26 09:31:52 +02:00
import { doNavigate } from 'redux/actions/navigation' ;
2018-10-19 22:38:07 +02:00
import { setSubscriptionLatest , doUpdateUnreadSubscriptions } from 'redux/actions/subscriptions' ;
import { makeSelectUnreadByChannel } from 'redux/selectors/subscriptions' ;
2017-12-28 00:48:11 +01:00
import { selectBadgeNumber } from 'redux/selectors/app' ;
2017-04-28 17:14:44 +02:00
import {
2018-04-18 06:03:01 +02:00
ACTIONS ,
SETTINGS ,
Lbry ,
Lbryapi ,
buildURI ,
makeSelectCostInfoForUri ,
2017-09-08 05:15:05 +02:00
makeSelectFileInfoForUri ,
2018-10-15 13:27:56 +02:00
selectFileInfosByOutpoint ,
2017-07-21 10:02:29 +02:00
selectDownloadingByOutpoint ,
2018-04-18 06:03:01 +02:00
selectBalance ,
2018-10-19 22:38:07 +02:00
makeSelectChannelForClaimUri ,
parseURI ,
2018-10-31 18:11:32 +01:00
creditsToString ,
2018-11-21 22:20:55 +01:00
doError ,
2018-04-18 06:03:01 +02:00
} from 'lbry-redux' ;
2018-08-01 20:49:57 +02:00
import { makeSelectClientSetting , selectosNotificationsEnabled } from 'redux/selectors/settings' ;
2018-11-21 22:20:55 +01:00
import setBadge from 'util/set-badge' ;
2018-09-12 21:50:04 +02:00
import analytics from 'analytics' ;
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 ) {
2017-12-28 00:48:11 +01:00
return ( dispatch , 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 ) ;
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-08-10 16:51:51 +02:00
const badgeNumber = selectBadgeNumber ( state ) ;
2017-12-21 18:32:51 +01:00
setBadge ( badgeNumber === 0 ? '' : ` ${ badgeNumber } ` ) ;
2017-06-24 18:48:01 +02:00
2019-01-28 21:05:28 +01:00
// Disabling this for now because it's confusing for new users that don't realize files are actually being downloaded
// This should move inside of the app
// const totalProgress = selectTotalDownloadProgress(state);
// setProgressBar(totalProgress);
2017-06-24 18:48:01 +02:00
2018-10-19 22:38:07 +02:00
const channelUri = makeSelectChannelForClaimUri ( uri , true ) ( state ) ;
const { claimName : channelName } = parseURI ( channelUri ) ;
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 ) ) {
2018-10-19 22:38:07 +02:00
const notif = new window . Notification ( channelName , {
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 = ( ) => {
dispatch (
doNavigate ( '/show' , {
uri ,
} )
) ;
} ;
}
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
2018-08-01 20:49:57 +02:00
if ( ! selectosNotificationsEnabled ( getState ( ) ) ) return ;
2018-10-19 22:38:07 +02:00
2018-03-26 09:31:52 +02:00
const notif = new window . Notification ( 'LBRY Download Complete' , {
body : fileInfo . metadata . title ,
silent : false ,
} ) ;
notif . onclick = ( ) => {
ipcRenderer . send ( 'focusWindow' , 'main' ) ;
} ;
}
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
2019-01-28 21:05:28 +01:00
// const totalProgress = selectTotalDownloadProgress(getState());
// setProgressBar(totalProgress);
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
} ;
2017-04-26 19:08:26 +02:00
}
2017-07-25 10:43:44 +02:00
export function doStartDownload ( uri , outpoint ) {
2017-12-28 00:48:11 +01:00
return ( dispatch , getState ) => {
2017-06-06 23:19:12 +02:00
const state = getState ( ) ;
2017-05-31 09:31:08 +02:00
2017-08-08 11:36:14 +02:00
if ( ! outpoint ) {
2017-12-21 18:32:51 +01:00
throw new Error ( 'outpoint is required to begin a download' ) ;
2017-08-08 11:36:14 +02:00
}
2017-07-30 21:20:36 +02:00
2017-07-25 10:43:44 +02:00
const { downloadingByOutpoint = { } } = state . fileInfo ;
2017-04-26 19:08:26 +02:00
2017-07-25 10:43:44 +02:00
if ( downloadingByOutpoint [ outpoint ] ) return ;
2017-04-26 19:08:26 +02:00
2017-12-21 18:32:51 +01:00
Lbry . file _list ( { outpoint , full _status : true } ) . then ( ( [ fileInfo ] ) => {
2017-07-25 10:43:44 +02:00
dispatch ( {
2017-12-21 18:32:51 +01:00
type : ACTIONS . DOWNLOADING _STARTED ,
2017-07-25 10:43:44 +02:00
data : {
uri ,
outpoint ,
fileInfo ,
} ,
2017-06-06 23:19:12 +02:00
} ) ;
2017-05-26 17:58:56 +02:00
2017-07-25 10:43:44 +02:00
dispatch ( doUpdateLoadStatus ( uri , outpoint ) ) ;
} ) ;
} ;
}
export function doDownloadFile ( uri , streamInfo ) {
2017-12-28 00:48:11 +01:00
return dispatch => {
2017-07-25 10:43:44 +02:00
dispatch ( doStartDownload ( uri , streamInfo . outpoint ) ) ;
2017-06-06 23:19:12 +02:00
} ;
2017-04-26 19:08:26 +02:00
}
2017-12-21 18:32:51 +01:00
export function doSetPlayingUri ( uri ) {
2017-12-28 00:48:11 +01:00
return 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
2018-08-03 17:54:10 +02:00
function handleLoadVideoError ( uri , errorType = '' ) {
return ( dispatch , getState ) => {
// suppress error when another media is playing
const { playingUri } = getState ( ) . content ;
2019-02-01 21:31:36 +01:00
const errorText = typeof errorType === 'object' ? errorType . message : errorType ;
2018-08-03 17:54:10 +02:00
if ( playingUri && playingUri === uri ) {
dispatch ( {
type : ACTIONS . LOADING _VIDEO _FAILED ,
data : { uri } ,
} ) ;
dispatch ( doSetPlayingUri ( null ) ) ;
2019-02-01 09:23:34 +01:00
// this is not working, but should be it's own separate modal in the future (https://github.com/lbryio/lbry-desktop/issues/892)
2018-08-03 17:54:10 +02:00
if ( errorType === 'timeout' ) {
2018-10-29 18:23:53 +01:00
doOpenModal ( MODALS . FILE _TIMEOUT , { uri } ) ;
2018-08-03 17:54:10 +02:00
} else {
dispatch (
2018-10-29 18:23:53 +01:00
doError (
2019-02-01 21:31:36 +01:00
` Failed to download ${ uri } , please try again or see error details: \n \n ${ errorText } \n \n If this problem persists, visit https://lbry.io/support for help. `
2018-08-03 17:54:10 +02:00
)
) ;
}
}
} ;
}
2018-09-12 21:50:04 +02:00
export function doLoadVideo ( uri , shouldRecordViewEvent ) {
2017-12-28 00:48:11 +01:00
return dispatch => {
2017-04-26 19:08:26 +02:00
dispatch ( {
2017-12-21 18:32:51 +01:00
type : ACTIONS . LOADING _VIDEO _STARTED ,
2017-04-26 19:08:26 +02:00
data : {
2017-06-06 23:19:12 +02:00
uri ,
} ,
} ) ;
2017-04-26 19:08:26 +02:00
2017-12-21 18:32:51 +01:00
Lbry . get ( { uri } )
2017-08-08 11:36:14 +02:00
. then ( streamInfo => {
2019-02-01 09:23:34 +01:00
// need error code from SDK to capture properly
2017-08-08 11:36:14 +02:00
const timeout =
2017-12-21 18:32:51 +01:00
streamInfo === null || typeof streamInfo !== 'object' || streamInfo . error === 'Timeout' ;
2017-08-08 11:36:14 +02:00
if ( timeout ) {
2018-07-12 21:37:01 +02:00
dispatch ( handleLoadVideoError ( uri , 'timeout' ) ) ;
2017-08-08 11:36:14 +02:00
} else {
dispatch ( doDownloadFile ( uri , streamInfo ) ) ;
2018-09-12 21:50:04 +02:00
if ( shouldRecordViewEvent ) {
analytics . apiLogView (
` ${ streamInfo . claim _name } # ${ streamInfo . claim _id } ` ,
streamInfo . outpoint ,
streamInfo . claim _id
) ;
}
2017-08-08 11:36:14 +02:00
}
} )
2019-02-01 09:23:34 +01:00
. catch ( error => {
dispatch ( handleLoadVideoError ( uri , error ) ) ;
2018-07-12 21:37:01 +02:00
} ) ;
} ;
}
2018-09-12 21:50:04 +02:00
export function doPurchaseUri ( uri , specificCostInfo , shouldRecordViewEvent ) {
2017-12-28 00:48:11 +01:00
return ( dispatch , getState ) => {
2017-06-06 23:19:12 +02:00
const state = getState ( ) ;
const balance = selectBalance ( state ) ;
2017-09-08 05:15:05 +02:00
const fileInfo = makeSelectFileInfoForUri ( uri ) ( state ) ;
2017-07-21 10:02:29 +02:00
const downloadingByOutpoint = selectDownloadingByOutpoint ( state ) ;
2017-12-21 18:32:51 +01:00
const alreadyDownloading = fileInfo && ! ! downloadingByOutpoint [ fileInfo . outpoint ] ;
2017-05-26 20:36:18 +02:00
2017-09-08 07:03:37 +02:00
function attemptPlay ( cost , instantPurchaseMax = null ) {
2019-02-18 18:33:02 +01:00
// If you have a file entry with correct manifest, you won't pay for the key fee again
if ( cost > 0 && ( ! instantPurchaseMax || cost > instantPurchaseMax ) && ! fileInfo ) {
2018-10-29 18:23:53 +01:00
dispatch ( doOpenModal ( MODALS . AFFIRM _PURCHASE , { uri } ) ) ;
2017-09-08 07:03:37 +02:00
} else {
2018-09-12 21:50:04 +02:00
dispatch ( doLoadVideo ( uri , shouldRecordViewEvent ) ) ;
2017-09-08 07:03:37 +02:00
}
2017-09-18 04:08:43 +02:00
}
// we already fully downloaded the file.
2017-09-08 07:03:37 +02:00
if ( fileInfo && fileInfo . completed ) {
2019-02-18 18:33:02 +01:00
// If path is null or bytes written is 0 means the user has deleted/moved the
2017-09-08 07:03:37 +02:00
// file manually on their file system, so we need to dispatch a
// doLoadVideo action to reconstruct the file from the blobs
2019-02-18 18:33:02 +01:00
if ( ! fileInfo . download _path || ! fileInfo . written _bytes )
dispatch ( doLoadVideo ( uri , shouldRecordViewEvent ) ) ;
2017-09-08 07:03:37 +02:00
2017-12-21 18:32:51 +01:00
Promise . resolve ( ) ;
return ;
2017-09-08 07:03:37 +02:00
}
// we are already downloading the file
if ( alreadyDownloading ) {
2017-12-21 18:32:51 +01:00
Promise . resolve ( ) ;
return ;
2017-04-27 17:10:39 +02:00
}
2018-03-06 08:44:36 +01:00
const costInfo = makeSelectCostInfoForUri ( uri ) ( state ) || specificCostInfo ;
2017-09-08 07:03:37 +02:00
const { cost } = costInfo ;
2017-04-27 09:05:41 +02:00
if ( cost > balance ) {
2017-10-01 04:50:32 +02:00
dispatch ( doSetPlayingUri ( null ) ) ;
2018-10-29 18:23:53 +01:00
dispatch ( doOpenModal ( MODALS . INSUFFICIENT _CREDITS ) ) ;
2017-12-21 18:32:51 +01:00
Promise . resolve ( ) ;
return ;
2017-09-08 07:03:37 +02:00
}
2017-12-21 18:32:51 +01:00
if ( cost === 0 || ! makeSelectClientSetting ( SETTINGS . INSTANT _PURCHASE _ENABLED ) ( state ) ) {
2017-09-08 07:03:37 +02:00
attemptPlay ( cost ) ;
} else {
2017-12-21 18:32:51 +01:00
const instantPurchaseMax = makeSelectClientSetting ( SETTINGS . INSTANT _PURCHASE _MAX ) ( state ) ;
if ( instantPurchaseMax . currency === 'LBC' ) {
2017-09-08 07:03:37 +02:00
attemptPlay ( cost , instantPurchaseMax . amount ) ;
} else {
// Need to convert currency of instant purchase maximum before trying to play
2018-04-18 06:03:01 +02:00
Lbryapi . getExchangeRates ( ) . then ( ( { LBC _USD } ) => {
2017-12-21 18:32:51 +01:00
attemptPlay ( cost , instantPurchaseMax . amount / LBC _USD ) ;
2017-09-08 07:03:37 +02:00
} ) ;
}
2017-04-26 19:08:26 +02:00
}
2017-06-06 23:19:12 +02:00
} ;
2017-04-26 19:08:26 +02:00
}
2017-05-13 00:50:51 +02:00
2019-01-11 17:34:36 +01:00
export function doFetchClaimsByChannel (
uri : string ,
page : number = 1 ,
pageSize : number = PAGE _SIZE
) {
2018-10-04 06:55:30 +02:00
return 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-01-11 17:34:36 +01:00
Lbry . claim _list _by _channel ( { uri , page , page _size : pageSize } ) . then ( result => {
const claimResult = result [ uri ] || { } ;
const { claims _in _channel : claimsInChannel , returned _page : returnedPage } = claimResult ;
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 (
{
channelName : latest . channel _name ,
uri : buildURI (
{
contentName : latest . channel _name ,
claimId : latest . value . publisherSignature . certificateId ,
} ,
false
) ,
} ,
buildURI ( { contentName : latest . name , claimId : latest . claim _id } , false )
)
) ;
}
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
}
2017-09-18 04:08:43 +02:00
export function doPlayUri ( uri ) {
2017-12-28 00:48:11 +01:00
return dispatch => {
2017-09-18 04:08:43 +02:00
dispatch ( doSetPlayingUri ( uri ) ) ;
dispatch ( doPurchaseUri ( uri ) ) ;
} ;
}
2017-07-10 16:49:12 +02:00
export function doFetchChannelListMine ( ) {
2017-12-28 00:48:11 +01:00
return dispatch => {
2017-07-10 16:49:12 +02:00
dispatch ( {
2018-02-21 06:41:30 +01:00
type : ACTIONS . FETCH _CHANNEL _LIST _STARTED ,
2017-07-10 16:49:12 +02:00
} ) ;
const callback = channels => {
dispatch ( {
2018-02-21 06:41:30 +01:00
type : ACTIONS . FETCH _CHANNEL _LIST _COMPLETED ,
2017-07-10 16:49:12 +02:00
data : { claims : channels } ,
} ) ;
} ;
2018-02-21 06:41:30 +01:00
Lbry . channel _list ( ) . then ( callback ) ;
2017-07-10 16:49:12 +02:00
} ;
}
2017-06-17 19:59:18 +02:00
2018-10-31 18:11:32 +01:00
export function doCreateChannel ( name : string , amount : number ) {
2017-12-28 00:48:11 +01:00
return dispatch => {
2017-06-17 19:59:18 +02:00
dispatch ( {
2017-12-21 18:32:51 +01:00
type : ACTIONS . CREATE _CHANNEL _STARTED ,
2017-06-17 19:59:18 +02:00
} ) ;
return new Promise ( ( resolve , reject ) => {
2017-12-21 18:32:51 +01:00
Lbry . channel _new ( {
channel _name : name ,
2018-10-31 18:11:32 +01:00
amount : creditsToString ( amount ) ,
2017-12-21 18:32:51 +01:00
} ) . then (
2018-01-02 20:54:57 +01:00
newChannelClaim => {
const channelClaim = newChannelClaim ;
channelClaim . name = name ;
2017-12-21 18:32:51 +01:00
dispatch ( {
type : ACTIONS . CREATE _CHANNEL _COMPLETED ,
2018-01-02 20:54:57 +01:00
data : { channelClaim } ,
2017-12-21 18:32:51 +01:00
} ) ;
2018-01-02 20:54:57 +01:00
resolve ( channelClaim ) ;
2017-12-21 18:32:51 +01:00
} ,
error => {
reject ( error ) ;
}
) ;
2017-06-17 19:59:18 +02:00
} ) ;
} ;
}
2018-07-31 14:36:20 +02:00
export function savePosition ( claimId : string , outpoint : string , position : number ) {
return dispatch => {
dispatch ( {
type : ACTIONS . SET _CONTENT _POSITION ,
data : { claimId , outpoint , position } ,
} ) ;
} ;
}
2018-07-31 20:07:45 +02:00
export function doSetContentHistoryItem ( uri : string ) {
return dispatch => {
dispatch ( {
type : ACTIONS . SET _CONTENT _LAST _VIEWED ,
data : { uri , lastViewed : Date . now ( ) } ,
} ) ;
} ;
}
export function doClearContentHistoryUri ( uri : string ) {
return dispatch => {
dispatch ( {
type : ACTIONS . CLEAR _CONTENT _HISTORY _URI ,
data : { uri } ,
} ) ;
} ;
}
2018-08-01 16:06:43 +02:00
export function doClearContentHistoryAll ( ) {
2018-07-31 20:07:45 +02:00
return dispatch => {
dispatch ( { type : ACTIONS . CLEAR _CONTENT _HISTORY _ALL } ) ;
} ;
}
2018-08-01 16:06:43 +02:00
export function doSetHistoryPage ( page ) {
return dispatch => {
dispatch ( {
type : ACTIONS . SET _CONTENT _HISTORY _PAGE ,
data : { page } ,
} ) ;
} ;
}