2017-12-08 21:14:35 +01:00
|
|
|
// @flow
|
2017-12-21 18:32:51 +01:00
|
|
|
import * as ACTIONS from 'constants/action_types';
|
2018-03-26 09:31:52 +02:00
|
|
|
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
|
|
|
import type {
|
|
|
|
Dispatch,
|
|
|
|
SubscriptionState,
|
|
|
|
SubscriptionNotifications,
|
|
|
|
} from 'redux/reducers/subscriptions';
|
2018-05-07 06:50:55 +02:00
|
|
|
import type { Subscription } from 'types/subscription';
|
2018-03-06 08:44:36 +01:00
|
|
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
2018-05-07 06:50:55 +02:00
|
|
|
import { Lbry, buildURI, parseURI } from 'lbry-redux';
|
2018-03-06 08:44:36 +01:00
|
|
|
import { doPurchaseUri } from 'redux/actions/content';
|
2018-05-07 06:50:55 +02:00
|
|
|
import Promise from 'bluebird';
|
|
|
|
import Lbryio from 'lbryio';
|
2018-03-06 08:44:36 +01:00
|
|
|
|
2018-03-12 06:34:16 +01:00
|
|
|
const CHECK_SUBSCRIPTIONS_INTERVAL = 60 * 60 * 1000;
|
2018-03-26 09:31:52 +02:00
|
|
|
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
|
2017-12-08 21:14:35 +01:00
|
|
|
|
2018-05-07 06:50:55 +02:00
|
|
|
export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () => any) => {
|
|
|
|
const {
|
|
|
|
subscriptions: subscriptionState,
|
|
|
|
settings: { daemonSettings },
|
|
|
|
} = getState();
|
|
|
|
const { subscriptions: reduxSubscriptions } = subscriptionState;
|
|
|
|
const { share_usage_data: isSharingData } = daemonSettings;
|
2017-12-08 21:14:35 +01:00
|
|
|
|
2018-05-07 06:50:55 +02:00
|
|
|
if (!isSharingData && isSharingData !== undefined) {
|
|
|
|
// They aren't sharing their data, subscriptions will be handled by persisted redux state
|
|
|
|
return;
|
|
|
|
}
|
2018-03-16 19:22:19 +01:00
|
|
|
|
2018-05-07 06:50:55 +02:00
|
|
|
// most of this logic comes from scenarios where the db isn't synced with redux
|
|
|
|
// this will happen if the user stops sharing data
|
|
|
|
dispatch({ type: ACTIONS.FETCH_SUBSCRIPTIONS_START });
|
|
|
|
|
|
|
|
Lbryio.call('subscription', 'list')
|
|
|
|
.then(dbSubscriptions => {
|
|
|
|
const storedSubscriptions = dbSubscriptions || [];
|
|
|
|
|
|
|
|
// User has no subscriptions in db or redux
|
|
|
|
if (!storedSubscriptions.length && (!reduxSubscriptions || !reduxSubscriptions.length)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
// There is some mismatch between redux state and db state
|
|
|
|
// If something is in the db, but not in redux, add it to redux
|
|
|
|
// If something is in redux, but not in the db, add it to the db
|
|
|
|
if (storedSubscriptions.length !== reduxSubscriptions.length) {
|
|
|
|
const dbSubMap = {};
|
|
|
|
const reduxSubMap = {};
|
|
|
|
const subsNotInDB = [];
|
|
|
|
const subscriptionsToReturn = reduxSubscriptions.slice();
|
|
|
|
|
|
|
|
storedSubscriptions.forEach(sub => {
|
|
|
|
dbSubMap[sub.claim_id] = 1;
|
|
|
|
});
|
|
|
|
|
|
|
|
reduxSubscriptions.forEach(sub => {
|
|
|
|
const { claimId } = parseURI(sub.uri);
|
|
|
|
reduxSubMap[claimId] = 1;
|
|
|
|
|
|
|
|
if (!dbSubMap[claimId]) {
|
|
|
|
subsNotInDB.push({
|
|
|
|
claim_id: claimId,
|
|
|
|
channel_name: sub.channelName,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
storedSubscriptions.forEach(sub => {
|
|
|
|
if (!reduxSubMap[sub.claim_id]) {
|
|
|
|
const uri = `lbry://${sub.channel_name}#${sub.claim_id}`;
|
|
|
|
subscriptionsToReturn.push({ uri, channelName: sub.channel_name });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return Promise.all(subsNotInDB.map(payload => Lbryio.call('subscription', 'new', payload)))
|
|
|
|
.then(() => subscriptionsToReturn)
|
|
|
|
.catch(
|
|
|
|
() =>
|
|
|
|
// let it fail, we will try again when the navigate to the subscriptions page
|
|
|
|
subscriptionsToReturn
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// DB is already synced, just return the subscriptions in redux
|
|
|
|
return reduxSubscriptions;
|
|
|
|
})
|
|
|
|
.then(subscriptions => {
|
|
|
|
dispatch({
|
|
|
|
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
|
|
|
|
data: subscriptions,
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.catch(() => {
|
|
|
|
dispatch({
|
|
|
|
type: ACTIONS.FETCH_SUBSCRIPTIONS_FAIL,
|
|
|
|
});
|
|
|
|
});
|
2018-03-16 19:26:44 +01:00
|
|
|
};
|
2018-03-16 19:22:19 +01:00
|
|
|
|
2018-05-07 06:50:55 +02:00
|
|
|
export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
|
|
|
|
dispatch: Dispatch
|
|
|
|
) =>
|
2017-12-13 22:36:30 +01:00
|
|
|
dispatch({
|
2018-05-07 06:50:55 +02:00
|
|
|
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
|
|
|
|
data: {
|
|
|
|
subscription,
|
|
|
|
uri,
|
|
|
|
},
|
2017-12-08 21:14:35 +01:00
|
|
|
});
|
|
|
|
|
2018-05-07 06:50:55 +02:00
|
|
|
export const setSubscriptionNotification = (
|
|
|
|
subscription: Subscription,
|
|
|
|
uri: string,
|
|
|
|
notificationType: string
|
|
|
|
) => (dispatch: Dispatch) =>
|
2018-03-06 08:44:36 +01:00
|
|
|
dispatch({
|
2018-05-07 06:50:55 +02:00
|
|
|
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
|
|
|
|
data: {
|
|
|
|
subscription,
|
|
|
|
uri,
|
|
|
|
type: notificationType,
|
|
|
|
},
|
2018-03-06 08:44:36 +01:00
|
|
|
});
|
|
|
|
|
2018-03-15 09:35:47 +01:00
|
|
|
export const doCheckSubscription = (subscription: Subscription, notify?: boolean) => (
|
|
|
|
dispatch: Dispatch
|
|
|
|
) => {
|
2018-03-06 08:44:36 +01:00
|
|
|
dispatch({
|
|
|
|
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
|
|
|
|
data: subscription,
|
|
|
|
});
|
|
|
|
|
|
|
|
Lbry.claim_list_by_channel({ uri: subscription.uri, page: 1 }).then(result => {
|
|
|
|
const claimResult = result[subscription.uri] || {};
|
|
|
|
const { claims_in_channel: claimsInChannel } = claimResult;
|
|
|
|
|
2018-03-26 09:31:52 +02:00
|
|
|
if (claimsInChannel) {
|
|
|
|
if (notify) {
|
|
|
|
claimsInChannel.reduce((prev, cur, index) => {
|
|
|
|
const uri = buildURI({ contentName: cur.name, claimId: cur.claim_id }, false);
|
|
|
|
if (prev === -1 && uri !== subscription.latest) {
|
|
|
|
dispatch(
|
|
|
|
setSubscriptionNotification(
|
|
|
|
subscription,
|
|
|
|
uri,
|
|
|
|
index < SUBSCRIPTION_DOWNLOAD_LIMIT && !cur.value.stream.metadata.fee
|
|
|
|
? NOTIFICATION_TYPES.DOWNLOADING
|
|
|
|
: NOTIFICATION_TYPES.NOTIFY_ONLY
|
|
|
|
)
|
|
|
|
);
|
|
|
|
if (index < SUBSCRIPTION_DOWNLOAD_LIMIT && !cur.value.stream.metadata.fee) {
|
|
|
|
dispatch(doPurchaseUri(uri, { cost: 0 }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return uri === subscription.latest || !subscription.latest ? index : prev;
|
|
|
|
}, -1);
|
2018-03-06 08:44:36 +01:00
|
|
|
}
|
|
|
|
|
2018-03-26 09:31:52 +02:00
|
|
|
dispatch(
|
|
|
|
setSubscriptionLatest(
|
|
|
|
{
|
|
|
|
channelName: claimsInChannel[0].channel_name,
|
2018-03-07 20:19:45 +01:00
|
|
|
uri: buildURI(
|
2018-03-26 09:31:52 +02:00
|
|
|
{
|
|
|
|
channelName: claimsInChannel[0].channel_name,
|
|
|
|
claimId: claimsInChannel[0].claim_id,
|
|
|
|
},
|
|
|
|
false
|
2018-03-07 20:19:45 +01:00
|
|
|
),
|
2018-03-26 09:31:52 +02:00
|
|
|
},
|
|
|
|
buildURI(
|
|
|
|
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
|
2018-03-15 09:35:47 +01:00
|
|
|
false
|
2018-03-26 09:31:52 +02:00
|
|
|
)
|
2018-03-15 09:35:47 +01:00
|
|
|
)
|
2018-03-26 09:31:52 +02:00
|
|
|
);
|
|
|
|
}
|
2018-03-15 09:34:22 +01:00
|
|
|
|
2018-03-06 08:44:36 +01:00
|
|
|
dispatch({
|
|
|
|
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
|
2018-03-06 09:36:04 +01:00
|
|
|
data: subscription,
|
2018-03-06 08:44:36 +01:00
|
|
|
});
|
|
|
|
});
|
2018-03-06 09:36:04 +01:00
|
|
|
};
|
2018-03-06 08:44:36 +01:00
|
|
|
|
2018-05-07 06:50:55 +02:00
|
|
|
export const setSubscriptionNotifications = (notifications: SubscriptionNotifications) => (
|
2018-03-06 09:36:04 +01:00
|
|
|
dispatch: Dispatch
|
|
|
|
) =>
|
|
|
|
dispatch({
|
2018-05-07 06:50:55 +02:00
|
|
|
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATIONS,
|
2018-03-06 01:28:11 +01:00
|
|
|
data: {
|
2018-05-07 06:50:55 +02:00
|
|
|
notifications,
|
2018-03-06 09:36:04 +01:00
|
|
|
},
|
2018-03-06 09:32:58 +01:00
|
|
|
});
|
2018-03-06 01:28:11 +01:00
|
|
|
|
2018-05-07 06:50:55 +02:00
|
|
|
export const doChannelSubscribe = (subscription: Subscription) => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: () => any
|
|
|
|
) => {
|
|
|
|
const {
|
|
|
|
settings: { daemonSettings },
|
|
|
|
} = getState();
|
|
|
|
const { share_usage_data: isSharingData } = daemonSettings;
|
|
|
|
|
2018-03-26 09:31:52 +02:00
|
|
|
dispatch({
|
2018-05-07 06:50:55 +02:00
|
|
|
type: ACTIONS.CHANNEL_SUBSCRIBE,
|
|
|
|
data: subscription,
|
2018-03-26 09:31:52 +02:00
|
|
|
});
|
|
|
|
|
2018-05-07 06:50:55 +02:00
|
|
|
// if the user isn't sharing data, keep the subscriptions entirely in the app
|
|
|
|
if (isSharingData) {
|
|
|
|
const { claimId } = parseURI(subscription.uri);
|
|
|
|
// They are sharing data, we can store their subscriptions in our internal database
|
|
|
|
Lbryio.call('subscription', 'new', {
|
|
|
|
channel_name: subscription.channelName,
|
|
|
|
claim_id: claimId,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch(doCheckSubscription(subscription, true));
|
|
|
|
};
|
|
|
|
|
|
|
|
export const doChannelUnsubscribe = (subscription: Subscription) => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: () => any
|
|
|
|
) => {
|
|
|
|
const {
|
|
|
|
settings: { daemonSettings },
|
|
|
|
} = getState();
|
|
|
|
const { share_usage_data: isSharingData } = daemonSettings;
|
|
|
|
|
2018-03-26 09:31:52 +02:00
|
|
|
dispatch({
|
2018-05-07 06:50:55 +02:00
|
|
|
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
|
|
|
|
data: subscription,
|
2018-03-26 09:31:52 +02:00
|
|
|
});
|
|
|
|
|
2018-05-07 06:50:55 +02:00
|
|
|
if (isSharingData) {
|
|
|
|
const { claimId } = parseURI(subscription.uri);
|
|
|
|
Lbryio.call('subscription', 'delete', {
|
|
|
|
claim_id: claimId,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const doCheckSubscriptions = () => (
|
|
|
|
dispatch: Dispatch,
|
|
|
|
getState: () => SubscriptionState
|
|
|
|
) => {
|
|
|
|
const checkSubscriptionsTimer = setInterval(
|
|
|
|
() =>
|
|
|
|
selectSubscriptions(getState()).map((subscription: Subscription) =>
|
|
|
|
dispatch(doCheckSubscription(subscription, true))
|
|
|
|
),
|
|
|
|
CHECK_SUBSCRIPTIONS_INTERVAL
|
|
|
|
);
|
|
|
|
dispatch({
|
|
|
|
type: ACTIONS.CHECK_SUBSCRIPTIONS_SUBSCRIBE,
|
|
|
|
data: { checkSubscriptionsTimer },
|
|
|
|
});
|
|
|
|
};
|