640237c630
## Ticket 725 ## Issue Upload a video. When `notify` is sent at the end of the tus upload, refresh immediately. The GUI allowed the user to resume the upload, but the ID is no longer present in the server. ## Approach Until the polling API for `notify` is available, we can only assume the best and let the user know how to handle it. - Store the "notify was sent" state. - Show a dialog explaining the situation. Thought of trying to make `claim_list` calls behind the scenes to clear itself, but it doesn't handle the case of `notify` actually failing. The best is to just let the user handle it for now. Note that for the case of `onerror` actually received, we still retry since a network error could be the culprit (`notify` wasn't sent).
267 lines
7.8 KiB
JavaScript
267 lines
7.8 KiB
JavaScript
// @flow
|
|
import { handleActions } from 'util/redux-utils';
|
|
import { buildURI } from 'util/lbryURI';
|
|
import { serializeFileObj } from 'util/file';
|
|
import {
|
|
tusLockAndNotify,
|
|
tusUnlockAndNotify,
|
|
tusRemoveAndNotify,
|
|
tusClearRemovedUploads,
|
|
tusClearLockedUploads,
|
|
} from 'util/tus';
|
|
import * as ACTIONS from 'constants/action_types';
|
|
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
|
import { CHANNEL_ANONYMOUS } from 'constants/claim';
|
|
|
|
// This is the old key formula. Retain it for now to allow users to delete
|
|
// any pending uploads. Can be removed from January 2022 onwards.
|
|
const getOldKeyFromParam = (params) => `${params.name}#${params.channel || 'anonymous'}`;
|
|
|
|
type PublishState = {
|
|
editingURI: ?string,
|
|
fileText: ?string,
|
|
filePath: ?string,
|
|
remoteFileUrl: ?string,
|
|
contentIsFree: boolean,
|
|
fileDur: number,
|
|
fileSize: number,
|
|
fileVid: boolean,
|
|
fee: {
|
|
amount: number,
|
|
currency: string,
|
|
},
|
|
title: string,
|
|
thumbnail_url: string,
|
|
thumbnailPath: string,
|
|
uploadThumbnailStatus: string,
|
|
thumbnailError: ?boolean,
|
|
description: string,
|
|
language: string,
|
|
releaseTime: ?number,
|
|
releaseTimeEdited: ?number,
|
|
releaseAnytime: boolean,
|
|
channel: string,
|
|
channelId: ?string,
|
|
name: string,
|
|
nameError: ?string,
|
|
bid: number,
|
|
bidError: ?string,
|
|
otherLicenseDescription: string,
|
|
licenseUrl: string,
|
|
tags: Array<string>,
|
|
optimize: boolean,
|
|
useLBRYUploader: boolean,
|
|
currentUploads: { [key: string]: FileUploadItem },
|
|
isMarkdownPost: boolean,
|
|
isLivestreamPublish: boolean,
|
|
};
|
|
|
|
const defaultState: PublishState = {
|
|
editingURI: undefined,
|
|
fileText: '',
|
|
filePath: undefined,
|
|
fileDur: 0,
|
|
fileSize: 0,
|
|
fileVid: false,
|
|
remoteFileUrl: undefined,
|
|
contentIsFree: true,
|
|
fee: {
|
|
amount: 1,
|
|
currency: 'LBC',
|
|
},
|
|
title: '',
|
|
thumbnail_url: '',
|
|
thumbnailPath: '',
|
|
uploadThumbnailStatus: THUMBNAIL_STATUSES.API_DOWN,
|
|
thumbnailError: undefined,
|
|
description: '',
|
|
language: '',
|
|
releaseTime: undefined,
|
|
releaseTimeEdited: undefined,
|
|
releaseTimeError: false,
|
|
releaseAnytime: false,
|
|
nsfw: false,
|
|
channel: CHANNEL_ANONYMOUS,
|
|
channelId: '',
|
|
name: '',
|
|
nameError: undefined,
|
|
bid: 0.01,
|
|
bidError: undefined,
|
|
licenseType: 'None',
|
|
otherLicenseDescription: 'All rights reserved',
|
|
licenseUrl: '',
|
|
tags: [],
|
|
publishing: false,
|
|
publishSuccess: false,
|
|
publishError: undefined,
|
|
optimize: false,
|
|
useLBRYUploader: false,
|
|
currentUploads: {},
|
|
isMarkdownPost: false,
|
|
isLivestreamPublish: false,
|
|
};
|
|
|
|
export const publishReducer = handleActions(
|
|
{
|
|
[ACTIONS.UPDATE_PUBLISH_FORM]: (state, action): PublishState => {
|
|
const { data } = action;
|
|
return {
|
|
...state,
|
|
...data,
|
|
};
|
|
},
|
|
[ACTIONS.CLEAR_PUBLISH]: (state: PublishState): PublishState => ({
|
|
...defaultState,
|
|
uri: undefined,
|
|
channel: state.channel,
|
|
bid: state.bid,
|
|
optimize: state.optimize,
|
|
language: state.language,
|
|
currentUploads: state.currentUploads,
|
|
}),
|
|
[ACTIONS.PUBLISH_START]: (state: PublishState): PublishState => ({
|
|
...state,
|
|
publishing: true,
|
|
publishSuccess: false,
|
|
}),
|
|
[ACTIONS.PUBLISH_FAIL]: (state: PublishState): PublishState => ({
|
|
...state,
|
|
publishing: false,
|
|
}),
|
|
[ACTIONS.PUBLISH_SUCCESS]: (state: PublishState): PublishState => ({
|
|
...state,
|
|
publishing: false,
|
|
publishSuccess: true,
|
|
}),
|
|
[ACTIONS.DO_PREPARE_EDIT]: (state: PublishState, action) => {
|
|
const { ...publishData } = action.data;
|
|
const { channel, name, uri } = publishData;
|
|
|
|
// The short uri is what is presented to the user
|
|
// The editingUri is the full uri with claim id
|
|
const shortUri = buildURI({
|
|
channelName: channel,
|
|
streamName: name,
|
|
});
|
|
|
|
return {
|
|
...defaultState,
|
|
...publishData,
|
|
editingURI: uri,
|
|
uri: shortUri,
|
|
currentUploads: state.currentUploads,
|
|
};
|
|
},
|
|
[ACTIONS.UPDATE_UPLOAD_ADD]: (state: PublishState, action) => {
|
|
const { file, params, uploader } = action.data;
|
|
const currentUploads = Object.assign({}, state.currentUploads);
|
|
|
|
currentUploads[params.guid] = {
|
|
file,
|
|
fileFingerprint: file ? serializeFileObj(file) : undefined, // TODO: get hash instead?
|
|
progress: '0',
|
|
params,
|
|
uploader,
|
|
resumable: !(uploader instanceof XMLHttpRequest),
|
|
};
|
|
|
|
return { ...state, currentUploads };
|
|
},
|
|
[ACTIONS.UPDATE_UPLOAD_PROGRESS]: (state: PublishState, action) => {
|
|
const { guid, progress, status } = action.data;
|
|
const key = guid;
|
|
const currentUploads = Object.assign({}, state.currentUploads);
|
|
|
|
if (guid === 'force--update') {
|
|
return { ...state, currentUploads };
|
|
} else if (guid === 'refresh--lock') {
|
|
// Re-lock all uploads that are in progress under our tab.
|
|
const uploadKeys = Object.keys(currentUploads);
|
|
uploadKeys.forEach((k) => {
|
|
if (currentUploads[k].uploader) {
|
|
tusLockAndNotify(k);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!currentUploads[key]) {
|
|
return state;
|
|
}
|
|
|
|
if (progress) {
|
|
currentUploads[key].progress = progress;
|
|
delete currentUploads[key].status;
|
|
|
|
if (currentUploads[key].uploader.url) {
|
|
// TUS has finally obtained an upload url from the server...
|
|
if (!currentUploads[key].params.uploadUrl) {
|
|
// ... Stash that to check later when resuming.
|
|
// Ignoring immutable-update requirement (probably doesn't matter to the GUI).
|
|
currentUploads[key].params.uploadUrl = currentUploads[key].uploader.url;
|
|
}
|
|
|
|
// ... lock this tab as the active uploader.
|
|
tusLockAndNotify(key);
|
|
}
|
|
} else if (status) {
|
|
currentUploads[key].status = status;
|
|
if (status === 'error' || status === 'conflict') {
|
|
delete currentUploads[key].uploader;
|
|
}
|
|
}
|
|
|
|
return { ...state, currentUploads };
|
|
},
|
|
[ACTIONS.UPDATE_UPLOAD_REMOVE]: (state: PublishState, action) => {
|
|
const { guid, params } = action.data;
|
|
const key = guid || getOldKeyFromParam(params);
|
|
|
|
if (state.currentUploads[key]) {
|
|
const currentUploads = Object.assign({}, state.currentUploads);
|
|
delete currentUploads[key];
|
|
tusUnlockAndNotify(key);
|
|
tusRemoveAndNotify(key);
|
|
|
|
return { ...state, currentUploads };
|
|
}
|
|
|
|
return state;
|
|
},
|
|
[ACTIONS.REHYDRATE]: (state: PublishState, action) => {
|
|
if (action && action.payload && action.payload.publish) {
|
|
const newPublish = { ...action.payload.publish };
|
|
|
|
// Cleanup for 'publish::currentUploads'
|
|
if (newPublish.currentUploads) {
|
|
const uploadKeys = Object.keys(newPublish.currentUploads);
|
|
if (uploadKeys.length > 0) {
|
|
// Clear uploader and corrupted params
|
|
uploadKeys.forEach((key) => {
|
|
const params = newPublish.currentUploads[key].params;
|
|
if (!params || Object.keys(params).length === 0) {
|
|
// The intended payload for the API is corrupted, so no point
|
|
// retaining. Remove from the pending-uploads list.
|
|
delete newPublish.currentUploads[key];
|
|
} else {
|
|
// The data is still good, so we can resume upload. We just need
|
|
// to delete the previous reference of the tus-uploader (no
|
|
// longer functional, will be re-created). An empty 'uploader'
|
|
// also tells the GUI that we just rebooted.
|
|
delete newPublish.currentUploads[key].uploader;
|
|
}
|
|
});
|
|
} else {
|
|
tusClearRemovedUploads();
|
|
}
|
|
|
|
tusClearLockedUploads();
|
|
}
|
|
|
|
return newPublish;
|
|
}
|
|
|
|
return state;
|
|
},
|
|
},
|
|
defaultState
|
|
);
|