@rpldy/uploader
Version:
the processing and queuing engine for react-uploady
256 lines • 9.07 kB
JavaScript
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 };