Temp revert to allow putting back the commits

This reverts commit 157b50c58e.
This commit is contained in:
infinite-persistence 2021-12-08 09:15:48 +08:00
parent fcf19a07e8
commit 994d9c6027
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
13 changed files with 58 additions and 286 deletions

View file

@ -62,8 +62,7 @@ declare type FileUploadSdkParams = {
remote_url?: string,
thumbnail_url?: string,
title?: string,
// Temporary values; remove when passing to SDK
guid: string,
// Temporary values
uploadUrl?: string,
};

View file

@ -1307,8 +1307,6 @@
"Uploading...": "Uploading...",
"Creating...": "Creating...",
"Stopped. Duplicate session detected.": "Stopped. Duplicate session detected.",
"File being uploaded in another tab or window.": "File being uploaded in another tab or window.",
"There are pending uploads.": "There are pending uploads.",
"Use a URL": "Use a URL",
"Edit Cover Image": "Edit Cover Image",
"Cover Image": "Cover Image",

View file

@ -2,7 +2,6 @@
import * as PAGES from 'constants/pages';
import React, { useEffect, useRef, useState, useLayoutEffect } from 'react';
import { lazyImport } from 'util/lazyImport';
import { tusUnlockAndNotify, tusHandleTabUpdates } from 'util/tus';
import classnames from 'classnames';
import analytics from 'analytics';
import { setSearchUserId } from 'redux/actions/search';
@ -232,29 +231,12 @@ function App(props: Props) {
useEffect(() => {
if (!uploadCount) return;
const handleUnload = (event) => tusUnlockAndNotify();
const handleBeforeUnload = (event) => {
event.preventDefault();
event.returnValue = __('There are pending uploads.'); // without setting this to something it doesn't work
event.returnValue = 'magic'; // without setting this to something it doesn't work
};
window.addEventListener('unload', handleUnload);
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('unload', handleUnload);
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [uploadCount]);
useEffect(() => {
if (!uploadCount) return;
const onStorageUpdate = (e) => tusHandleTabUpdates(e.key);
window.addEventListener('storage', onStorageUpdate);
return () => window.removeEventListener('storage', onStorageUpdate);
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
}, [uploadCount]);
// allows user to pause miniplayer using the spacebar without the page scrolling down

View file

@ -5,12 +5,11 @@ import Button from 'component/button';
import FileThumbnail from 'component/fileThumbnail';
import * as MODALS from 'constants/modal_types';
import { serializeFileObj } from 'util/file';
import { tusIsSessionLocked } from 'util/tus';
type Props = {
uploadItem: FileUploadItem,
doPublishResume: (any) => void,
doUpdateUploadRemove: (string, any) => void,
doUpdateUploadRemove: (any) => void,
doOpenModal: (string, {}) => void,
};
@ -19,16 +18,11 @@ export default function WebUploadItem(props: Props) {
const { params, file, fileFingerprint, progress, status, resumable, uploader } = uploadItem;
const [showFileSelector, setShowFileSelector] = useState(false);
const locked = tusIsSessionLocked(params.guid);
function handleFileChange(newFile: WebFile, clearName = true) {
if (serializeFileObj(newFile) === fileFingerprint) {
setShowFileSelector(false);
doPublishResume({ ...params, file_path: newFile });
if (!params.guid) {
// Can remove this if-block after January 2022.
doUpdateUploadRemove('', params);
}
} else {
doOpenModal(MODALS.CONFIRM, {
title: __('Invalid file'),
@ -46,34 +40,21 @@ export default function WebUploadItem(props: Props) {
subtitle: __('Cancel and remove the selected upload?'),
body: params.name ? <p className="empty">{`lbry://${params.name}`}</p> : undefined,
onConfirm: (closeModal) => {
if (tusIsSessionLocked(params.guid)) {
// Corner-case: it's possible for the upload to resume in another tab
// after the modal has appeared. Make a final lock-check here.
// We can invoke a toast here, but just do nothing for now.
// The upload status should make things obvious.
} else {
if (uploader) {
if (resumable) {
// $FlowFixMe - couldn't resolve to TusUploader manually.
uploader.abort(true); // TUS
} else {
uploader.abort(); // XHR
}
if (uploader) {
if (resumable) {
// $FlowFixMe - couldn't resolve to TusUploader manually.
uploader.abort(true); // TUS
} else {
uploader.abort(); // XHR
}
// The second parameter (params) can be removed after January 2022.
doUpdateUploadRemove(params.guid, params);
}
doUpdateUploadRemove(params);
closeModal();
},
});
}
function resolveProgressStr() {
if (locked) {
return __('File being uploaded in another tab or window.');
}
if (!uploader) {
return __('Stopped.');
}
@ -100,7 +81,7 @@ export default function WebUploadItem(props: Props) {
}
function getRetryButton() {
if (!resumable || locked) {
if (!resumable) {
return null;
}
@ -128,9 +109,7 @@ export default function WebUploadItem(props: Props) {
}
function getCancelButton() {
if (!locked) {
return <Button label={__('Cancel')} button="link" onClick={handleCancel} />;
}
return <Button label={__('Cancel')} button="link" onClick={handleCancel} />;
}
function getFileSelector() {

View file

@ -7,7 +7,7 @@ type Props = {
currentUploads: { [key: string]: FileUploadItem },
uploadCount: number,
doPublishResume: (any) => void,
doUpdateUploadRemove: (string, any) => void,
doUpdateUploadRemove: (any) => void,
doOpenModal: (string, {}) => void,
};

View file

@ -1,3 +0,0 @@
// Local Storage keys
export const TUS_LOCKED_UPLOADS = 'tusLockedUploads';
export const TUS_REMOVED_UPLOADS = 'tusRemovedUploads';

View file

@ -702,28 +702,21 @@ export function doUpdateUploadAdd(
};
}
export const doUpdateUploadProgress = (props: { guid: string, progress?: string, status?: string }) => (
dispatch: Dispatch
) =>
export const doUpdateUploadProgress = (props: {
params: { [key: string]: any },
progress?: string,
status?: string,
}) => (dispatch: Dispatch) =>
dispatch({
type: ACTIONS.UPDATE_UPLOAD_PROGRESS,
data: props,
});
/**
* doUpdateUploadRemove
*
* @param guid
* @param params Optional. Retain to allow removal of old keys, which are
* derived from `name#channel` instead of using a guid.
* Can be removed after January 2022.
* @returns {(function(Dispatch, GetState): void)|*}
*/
export function doUpdateUploadRemove(guid: string, params?: { [key: string]: any }) {
export function doUpdateUploadRemove(params: { [key: string]: any }) {
return (dispatch: Dispatch, getState: GetState) => {
dispatch({
type: ACTIONS.UPDATE_UPLOAD_REMOVE,
data: { guid, params },
data: { params },
});
};
}

View file

@ -2,14 +2,11 @@
import { handleActions } from 'util/redux-utils';
import { buildURI } from 'util/lbryURI';
import { serializeFileObj } from 'util/file';
import { tusLockAndNotify, tusUnlockAndNotify, tusRemoveAndNotify, tusClearRemovedUploads } 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'}`;
const getKeyFromParam = (params) => `${params.name}#${params.channel || 'anonymous'}`;
type PublishState = {
editingURI: ?string,
@ -141,9 +138,10 @@ export const publishReducer = handleActions(
},
[ACTIONS.UPDATE_UPLOAD_ADD]: (state: PublishState, action) => {
const { file, params, uploader } = action.data;
const key = getKeyFromParam(params);
const currentUploads = Object.assign({}, state.currentUploads);
currentUploads[params.guid] = {
currentUploads[key] = {
file,
fileFingerprint: file ? serializeFileObj(file) : undefined, // TODO: get hash instead?
progress: '0',
@ -155,14 +153,10 @@ export const publishReducer = handleActions(
return { ...state, currentUploads };
},
[ACTIONS.UPDATE_UPLOAD_PROGRESS]: (state: PublishState, action) => {
const { guid, progress, status } = action.data;
const key = guid;
const { params, progress, status } = action.data;
const key = getKeyFromParam(params);
const currentUploads = Object.assign({}, state.currentUploads);
if (guid === 'force--update') {
return { ...state, currentUploads };
}
if (!currentUploads[key]) {
return state;
}
@ -171,16 +165,10 @@ export const publishReducer = handleActions(
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);
if (currentUploads[key].uploader.url && !currentUploads[key].params.uploadUrl) {
// TUS has finally obtained an upload url from the server. 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;
}
} else if (status) {
currentUploads[key].status = status;
@ -192,19 +180,13 @@ export const publishReducer = handleActions(
return { ...state, currentUploads };
},
[ACTIONS.UPDATE_UPLOAD_REMOVE]: (state: PublishState, action) => {
const { guid, params } = action.data;
const key = guid || getOldKeyFromParam(params);
const { params } = action.data;
const key = getKeyFromParam(params);
const currentUploads = Object.assign({}, state.currentUploads);
if (state.currentUploads[key]) {
const currentUploads = Object.assign({}, state.currentUploads);
delete currentUploads[key];
tusUnlockAndNotify(key);
tusRemoveAndNotify(key);
delete currentUploads[key];
return { ...state, currentUploads };
}
return state;
return { ...state, currentUploads };
},
[ACTIONS.REHYDRATE]: (state: PublishState, action) => {
if (action && action.payload && action.payload.publish) {
@ -212,20 +194,14 @@ export const publishReducer = handleActions(
// 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) {
delete newPublish.currentUploads[key];
} else {
delete newPublish.currentUploads[key].uploader;
}
});
} else {
tusClearRemovedUploads();
}
Object.keys(newPublish.currentUploads).forEach((key) => {
const params = newPublish.currentUploads[key].params;
if (!params || Object.keys(params).length === 0) {
delete newPublish.currentUploads[key];
} else {
delete newPublish.currentUploads[key].uploader;
}
});
}
return newPublish;

View file

@ -1,15 +0,0 @@
export function isLocalStorageAvailable() {
try {
return Boolean(window.localStorage);
} catch (e) {
return false;
}
}
export function isSessionStorageAvailable() {
try {
return Boolean(window.sessionStorage);
} catch (e) {
return false;
}
}

View file

@ -1,129 +0,0 @@
// @flow
/**
* This serves a bridge between tabs using localStorage to indicate whether an
* upload is currently in progress (locked) or removed.
*
* An alternative is to sync the redux's 'publish::currentUploads' through the
* wallet's sync process, but let's not pollute the wallet for now.
*/
import { v4 as uuid } from 'uuid';
import { TUS_LOCKED_UPLOADS, TUS_REMOVED_UPLOADS } from 'constants/storage';
import { isLocalStorageAvailable } from 'util/storage';
import { doUpdateUploadRemove, doUpdateUploadProgress } from 'redux/actions/publish';
const localStorageAvailable = isLocalStorageAvailable();
let gTabId: string = '';
function getTabId() {
if (!gTabId) {
// We want to maximize bootup speed, so only initialize
// the tab ID on first use instead when declared.
gTabId = uuid();
}
return gTabId;
}
// ****************************************************************************
// Locked
// ****************************************************************************
function getLockedUploads() {
if (localStorageAvailable) {
const storedValue = window.localStorage.getItem(TUS_LOCKED_UPLOADS);
return storedValue ? JSON.parse(storedValue) : {};
}
return {};
}
export function tusIsSessionLocked(guid: string) {
const lockedUploads = getLockedUploads();
return lockedUploads[guid] && lockedUploads[guid] !== getTabId();
}
export function tusLockAndNotify(guid: string) {
const lockedUploads = getLockedUploads();
if (!lockedUploads[guid] && localStorageAvailable) {
lockedUploads[guid] = getTabId();
window.localStorage.setItem(TUS_LOCKED_UPLOADS, JSON.stringify(lockedUploads));
}
}
/**
* tusUnlockAndNotify
*
* @param guid The upload session to unlock and notify other tabs of.
* Passing 'undefined' will clear all sessions locked by this tab.
*/
export function tusUnlockAndNotify(guid?: string) {
if (!localStorageAvailable) return;
const lockedUploads = getLockedUploads();
if (guid) {
delete lockedUploads[guid];
} else {
const ourTabId = getTabId();
const lockedUploadsEntries = Object.entries(lockedUploads);
lockedUploadsEntries.forEach(([lockedGuid, tabId]) => {
if (tabId === ourTabId) {
delete lockedUploads[lockedGuid];
}
});
}
if (Object.keys(lockedUploads).length > 0) {
window.localStorage.setItem(TUS_LOCKED_UPLOADS, JSON.stringify(lockedUploads));
} else {
window.localStorage.removeItem(TUS_LOCKED_UPLOADS);
}
}
// ****************************************************************************
// Removed
// ****************************************************************************
function getRemovedUploads() {
if (localStorageAvailable) {
const storedValue = window.localStorage.getItem(TUS_REMOVED_UPLOADS);
return storedValue ? storedValue.split(',') : [];
}
return [];
}
export function tusRemoveAndNotify(guid: string) {
if (!localStorageAvailable) return;
const removedUploads = getRemovedUploads();
removedUploads.push(guid);
window.localStorage.setItem(TUS_REMOVED_UPLOADS, removedUploads.join(','));
}
export function tusClearRemovedUploads() {
if (!localStorageAvailable) return;
window.localStorage.removeItem(TUS_REMOVED_UPLOADS);
}
// ****************************************************************************
// Respond to changes from other tabs.
// ****************************************************************************
export function tusHandleTabUpdates(storageKey: string) {
switch (storageKey) {
case TUS_LOCKED_UPLOADS:
// The locked IDs are in localStorage, but related GUI is unaware.
// Send a redux update to force an update.
window.store.dispatch(doUpdateUploadProgress({ guid: 'force--update' }));
break;
case TUS_REMOVED_UPLOADS:
// The other tab's store has removed this upload, so it's safe to do the
// same without affecting rehydration.
if (localStorageAvailable) {
const removedUploads = getRemovedUploads();
removedUploads.forEach((guid) => window.store.dispatch(doUpdateUploadRemove(guid)));
}
break;
}
}

View file

@ -30,12 +30,10 @@ export function makeUploadRequest(
delete params['remote_url'];
}
const { uploadUrl, guid, ...sdkParams } = params;
const jsonPayload = JSON.stringify({
jsonrpc: '2.0',
method: ENDPOINT_METHOD,
params: sdkParams,
params,
id: new Date().getTime(),
});
@ -49,18 +47,18 @@ export function makeUploadRequest(
xhr.responseType = 'json';
xhr.upload.onprogress = (e) => {
const percentage = ((e.loaded / e.total) * 100).toFixed(2);
window.store.dispatch(doUpdateUploadProgress({ guid, progress: percentage }));
window.store.dispatch(doUpdateUploadProgress({ params, progress: percentage }));
};
xhr.onload = () => {
window.store.dispatch(doUpdateUploadRemove(guid));
window.store.dispatch(doUpdateUploadRemove(params));
resolve(xhr);
};
xhr.onerror = () => {
window.store.dispatch(doUpdateUploadProgress({ guid, status: 'error' }));
window.store.dispatch(doUpdateUploadProgress({ params, status: 'error' }));
reject(new Error(__('There was a problem with your upload. Please try again.')));
};
xhr.onabort = () => {
window.store.dispatch(doUpdateUploadRemove(guid));
window.store.dispatch(doUpdateUploadRemove(params));
};
if (!isPreview) {

View file

@ -38,12 +38,13 @@ export function makeResumableUploadRequest(
reject(new Error('Publish: v2 does not support remote_url'));
}
const { uploadUrl, guid, ...sdkParams } = params;
const payloadParams = Object.assign({}, params);
delete payloadParams.uploadUrl; // cleanup
const jsonPayload = JSON.stringify({
jsonrpc: '2.0',
method: RESUMABLE_ENDPOINT_METHOD,
params: sdkParams,
params: payloadParams,
id: new Date().getTime(),
});
@ -69,7 +70,7 @@ export function makeResumableUploadRequest(
filetype: file instanceof File ? file.type : undefined,
},
onShouldRetry: (err, retryAttempt, options) => {
window.store.dispatch(doUpdateUploadProgress({ guid, status: 'retry' }));
window.store.dispatch(doUpdateUploadProgress({ params, status: 'retry' }));
const status = err.originalResponse ? err.originalResponse.getStatus() : 0;
return !inStatusCategory(status, 400);
},
@ -78,17 +79,17 @@ export function makeResumableUploadRequest(
const errMsg = typeof err === 'string' ? err : err.message;
if (status === STATUS_CONFLICT || status === STATUS_LOCKED || errMsg === 'file currently locked') {
window.store.dispatch(doUpdateUploadProgress({ guid, status: 'conflict' }));
window.store.dispatch(doUpdateUploadProgress({ params, status: 'conflict' }));
// prettier-ignore
reject(new Error(`${status}: concurrent upload detected. Uploading the same file from multiple tabs or windows is not allowed.`));
} else {
window.store.dispatch(doUpdateUploadProgress({ guid, status: 'error' }));
window.store.dispatch(doUpdateUploadProgress({ params, status: 'error' }));
reject(new Error(err));
}
},
onProgress: (bytesUploaded, bytesTotal) => {
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
window.store.dispatch(doUpdateUploadProgress({ guid, progress: percentage }));
window.store.dispatch(doUpdateUploadProgress({ params, progress: percentage }));
},
onSuccess: () => {
let retries = 1;
@ -101,7 +102,7 @@ export function makeResumableUploadRequest(
xhr.setRequestHeader(X_LBRY_AUTH_TOKEN, token);
xhr.responseType = 'json';
xhr.onload = () => {
window.store.dispatch(doUpdateUploadRemove(guid));
window.store.dispatch(doUpdateUploadRemove(params));
resolve(xhr);
};
xhr.onerror = () => {
@ -110,12 +111,12 @@ export function makeResumableUploadRequest(
analytics.error('notify: first attempt failed (status=0). Retrying after 10s...');
setTimeout(() => makeNotifyRequest(), 10000); // Auto-retry after 10s delay.
} else {
window.store.dispatch(doUpdateUploadProgress({ guid, status: 'error' }));
window.store.dispatch(doUpdateUploadProgress({ params, status: 'error' }));
reject(new Error(`There was a problem in the processing. Please retry. (${xhr.status})`));
}
};
xhr.onabort = () => {
window.store.dispatch(doUpdateUploadRemove(guid));
window.store.dispatch(doUpdateUploadRemove(params));
};
xhr.send(jsonPayload);

View file

@ -1,6 +1,5 @@
// @flow
import * as tus from 'tus-js-client';
import { v4 as uuid } from 'uuid';
import { makeUploadRequest } from './publish-v1';
import { makeResumableUploadRequest } from './publish-v2';
@ -35,12 +34,6 @@ export default function apiPublishCallViaWeb(
params.file_path = '__POST_FILE__';
}
// Add a random ID to serve as the redux key.
// If it already exists, then it is a resumed session.
if (!params.guid) {
params.guid = uuid();
}
const useV1 = remoteUrl || preview || !tus.isSupported;
// Note: both function signature (params) should match.