lbry-desktop/ui/util/tus.js
infinite-persistence 1cc2132a28 Uploads: prevent perpetual locked upload
## Issue
- Closes 592 Force clear stuck upload
- It was possible for an upload to stay "locked" e.g. when browser is killed.

## Change
When refreshing or opening a new tab, always clear the locks. The on-going sessions will re-lock them immediately.
2022-01-03 12:10:55 -05:00

139 lines
4.4 KiB
JavaScript

// @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_REFRESH_LOCK, 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);
}
export function tusClearLockedUploads() {
if (!localStorageAvailable) return;
window.localStorage.removeItem(TUS_LOCKED_UPLOADS);
window.localStorage.setItem(TUS_REFRESH_LOCK, Math.random());
}
// ****************************************************************************
// 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_REFRESH_LOCK:
window.store.dispatch(doUpdateUploadProgress({ guid: 'refresh--lock' }));
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;
}
}