UNPKG

@rpldy/uploader

Version:

the processing and queuing engine for react-uploady

256 lines 9.07 kB
import { BATCH_STATES, logger, merge, FILE_STATES, scheduleIdleWork } from "@rpldy/shared"; import { unwrap } from "@rpldy/simple-state"; import { UPLOADER_EVENTS } from "../consts"; import { getItemsPrepareUpdater } from "./preSendPrepare"; import { finalizeItem, getIsItemExists } from "./itemHelpers"; const prepareBatchStartItems = getItemsPrepareUpdater(UPLOADER_EVENTS.BATCH_START, batch => batch.items, null, ({ batch } = { batch: false }) => { if (batch) { throw new Error(`BATCH_START event handlers cannot update batch data. Only items & options`); } }); const BATCH_READY_STATES = [BATCH_STATES.ADDED, BATCH_STATES.PROCESSING, BATCH_STATES.UPLOADING]; const BATCH_FINISHED_STATES = [BATCH_STATES.ABORTED, BATCH_STATES.CANCELLED, BATCH_STATES.FINISHED, BATCH_STATES.ERROR]; const getBatchFromState = (state, id) => state.batches[id].batch; const getBatch = (queue, id) => { return getBatchFromState(queue.getState(), id); }; const getBatchDataFromItemId = (queue, itemId) => { const state = queue.getState(); const item = state.items[itemId]; return state.batches[item.batchId]; }; const getBatchFromItemId = (queue, itemId) => { return getBatchDataFromItemId(queue, itemId).batch; }; const removeBatchItems = (queue, batchId) => { const batch = getBatch(queue, batchId); batch.items.forEach(({ id }) => finalizeItem(queue, id, true)); }; const removeBatch = (queue, batchId) => { queue.updateState(state => { delete state.batches[batchId]; delete state.itemQueue[batchId]; const batchQueueIndex = state.batchQueue.indexOf(batchId); if (~batchQueueIndex) { state.batchQueue.splice(batchQueueIndex, 1); } const pendingFlagIndex = state.batchesStartPending.indexOf(batchId); if (~pendingFlagIndex) { state.batchesStartPending.splice(pendingFlagIndex, 1); } }); }; const finalizeBatch = (queue, batchId, eventType, finalState = BATCH_STATES.FINISHED, additionalInfo) => { queue.updateState(state => { const batch = getBatchFromState(state, batchId); batch.state = finalState; if (additionalInfo) { batch.additionalInfo = additionalInfo; } }); triggerUploaderBatchEvent(queue, batchId, eventType); triggerUploaderBatchEvent(queue, batchId, UPLOADER_EVENTS.BATCH_FINALIZE); }; const cancelBatchWithId = (queue, batchId) => { logger.debugLog("uploady.uploader.batchHelpers: cancelling batch: ", batchId); finalizeBatch(queue, batchId, UPLOADER_EVENTS.BATCH_CANCEL, BATCH_STATES.CANCELLED); removeBatchItems(queue, batchId); removeBatch(queue, batchId); }; const cancelBatchForItem = (queue, itemId) => { if (getIsItemExists(queue, itemId)) { const data = getBatchDataFromItemId(queue, itemId), batchId = data?.batch.id; if (batchId) { cancelBatchWithId(queue, batchId); } else { logger.debugLog(`uploady.uploader.batchHelpers: cancel batch called for batch already removed (item id = ${itemId})`); } } }; const failBatchForItem = (queue, itemId, err) => { const batch = getBatchFromItemId(queue, itemId), batchId = batch.id; logger.debugLog("uploady.uploader.batchHelpers: failing batch: ", { batch }); finalizeBatch(queue, batchId, UPLOADER_EVENTS.BATCH_ERROR, BATCH_STATES.ERROR, err.message); removeBatchItems(queue, batchId); removeBatch(queue, batchId); }; const isItemBatchStartPending = (queue, itemId) => { const batch = getBatchFromItemId(queue, itemId); return queue.getState().batchesStartPending.includes(batch.id); }; const isNewBatchStarting = (queue, itemId) => { const batch = getBatchFromItemId(queue, itemId); return queue.getState().currentBatch !== batch.id; }; const loadNewBatchForItem = (queue, itemId) => { const batch = getBatchFromItemId(queue, itemId); queue.updateState(state => { state.batchesStartPending.push(batch.id); }); return prepareBatchStartItems(queue, batch).then(({ cancelled }) => { let alreadyFinished = false; queue.updateState(state => { const pendingFlagIndex = state.batchesStartPending.indexOf(batch.id); state.batchesStartPending.splice(pendingFlagIndex, 1); }); if (!cancelled) { alreadyFinished = !getIsItemExists(queue, itemId); if (!alreadyFinished) { queue.updateState(state => { state.currentBatch = batch.id; }); } } return !cancelled && !alreadyFinished; }); }; const cleanUpFinishedBatches = queue => { scheduleIdleWork(() => { const state = queue.getState(); Object.keys(state.batches).forEach(batchId => { const { batch, finishedCounter } = state.batches[batchId]; const { orgItemCount } = batch; const alreadyFinalized = getIsBatchFinalized(batch); if (orgItemCount === finishedCounter) { if (!alreadyFinalized && batch.completed !== 100) { queue.updateState(state => { const batch = getBatchFromState(state, batchId); batch.completed = 100; batch.loaded = batch.items.reduce((res, { loaded }) => res + loaded, 0); }); triggerUploaderBatchEvent(queue, batchId, UPLOADER_EVENTS.BATCH_PROGRESS); } queue.updateState(state => { if (state.currentBatch === batchId) { state.currentBatch = null; } }); logger.debugLog(`uploady.uploader.batchHelpers: cleaning up batch: ${batch.id}`); if (!alreadyFinalized) { finalizeBatch(queue, batchId, UPLOADER_EVENTS.BATCH_FINISH); } removeBatchItems(queue, batchId); removeBatch(queue, batchId); } }); }); }; const triggerUploaderBatchEvent = (queue, batchId, event) => { const state = queue.getState(), { batch, batchOptions } = state.batches[batchId], stateItems = state.items; const eventBatch = { ...unwrap(batch), items: batch.items.map(({ id }) => unwrap(stateItems[id])) }; queue.trigger(event, eventBatch, unwrap(batchOptions)); }; const getIsBatchReady = (queue, batchId) => { const batch = getBatchFromState(queue.getState(), batchId); return BATCH_READY_STATES.includes(batch.state); }; const detachRecycledFromPreviousBatch = (queue, item) => { const { previousBatch } = item; if (item.recycled && previousBatch && queue.getState().batches[previousBatch]) { const { id: batchId } = getBatchFromItemId(queue, item.id); if (batchId === previousBatch) { queue.updateState(state => { const batch = getBatchFromState(state, batchId); const index = batch.items.findIndex(({ id }) => id === item.id); if (~index) { batch.items.splice(index, 1); } if (state.batches[batchId].itemBatchOptions[item.id]) { delete state.batches[batchId].itemBatchOptions[item.id]; } }); } } }; const preparePendingForUpload = (queue, uploadOptions) => { queue.updateState(state => { Object.keys(state.batches).forEach(batchId => { const batchData = state.batches[batchId]; const { batch, batchOptions } = batchData; if (batch.state === BATCH_STATES.PENDING) { batch.items.forEach(item => { item.state = FILE_STATES.ADDED; }); batch.state = BATCH_STATES.ADDED; batchData.batchOptions = merge({}, batchOptions, uploadOptions); } }); }); }; const removePendingBatches = queue => { const batches = queue.getState().batches; Object.keys(batches).filter(batchId => batches[batchId].batch.state === BATCH_STATES.PENDING).forEach(batchId => { removeBatchItems(queue, batchId); removeBatch(queue, batchId); }); }; const incrementBatchFinishedCounter = (queue, batchId) => { queue.updateState(state => { state.batches[batchId].finishedCounter += 1; }); }; const getIsBatchFinalized = batch => BATCH_FINISHED_STATES.includes(batch.state); const clearBatchData = (queue, batchId) => { queue.updateState(state => { const { items } = getBatchFromState(state, batchId); delete state.batches[batchId]; delete state.itemQueue[batchId]; const indx = state.batchQueue.indexOf(batchId); if (~indx) { state.batchQueue.splice(indx, 1); } if (state.currentBatch === batchId) { state.currentBatch = null; } items.forEach(({ id }) => { delete state.items[id]; const activeIndex = state.activeIds.indexOf(id); if (~activeIndex) { state.activeIds.splice(activeIndex, 1); } }); }); }; export { loadNewBatchForItem, isNewBatchStarting, cancelBatchWithId, cancelBatchForItem, getBatchFromItemId, getBatchDataFromItemId, cleanUpFinishedBatches, triggerUploaderBatchEvent, getIsBatchReady, getBatchFromState, detachRecycledFromPreviousBatch, preparePendingForUpload, removePendingBatches, incrementBatchFinishedCounter, getIsBatchFinalized, failBatchForItem, finalizeBatch, removeBatchItems, clearBatchData, isItemBatchStartPending };