import _ from 'lodash'; import { createAction } from 'redux-actions'; import { batchActions } from 'redux-batched-actions'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import getNewSeries from 'Utilities/Series/getNewSeries'; import * as seriesTypes from 'Utilities/Series/seriesTypes'; import getSectionState from 'Utilities/State/getSectionState'; import updateSectionState from 'Utilities/State/updateSectionState'; import { removeItem, set, updateItem } from './baseActions'; import createHandleActions from './Creators/createHandleActions'; import { fetchRootFolders } from './rootFolderActions'; // // Variables export const section = 'importSeries'; let concurrentLookups = 0; let abortCurrentLookup = null; const queue = []; // // State export const defaultState = { isLookingUpSeries: false, isImporting: false, isImported: false, importError: null, items: [] }; // // Actions Types export const QUEUE_LOOKUP_SERIES = 'importSeries/queueLookupSeries'; export const START_LOOKUP_SERIES = 'importSeries/startLookupSeries'; export const CANCEL_LOOKUP_SERIES = 'importSeries/cancelLookupSeries'; export const LOOKUP_UNSEARCHED_SERIES = 'importSeries/lookupUnsearchedSeries'; export const CLEAR_IMPORT_SERIES = 'importSeries/clearImportSeries'; export const SET_IMPORT_SERIES_VALUE = 'importSeries/setImportSeriesValue'; export const IMPORT_SERIES = 'importSeries/importSeries'; // // Action Creators export const queueLookupSeries = createThunk(QUEUE_LOOKUP_SERIES); export const startLookupSeries = createThunk(START_LOOKUP_SERIES); export const importSeries = createThunk(IMPORT_SERIES); export const lookupUnsearchedSeries = createThunk(LOOKUP_UNSEARCHED_SERIES); export const clearImportSeries = createAction(CLEAR_IMPORT_SERIES); export const cancelLookupSeries = createAction(CANCEL_LOOKUP_SERIES); export const setImportSeriesValue = createAction(SET_IMPORT_SERIES_VALUE, (payload) => { return { section, ...payload }; }); // // Action Handlers export const actionHandlers = handleThunks({ [QUEUE_LOOKUP_SERIES]: function(getState, payload, dispatch) { const { name, path, relativePath, term, topOfQueue = false } = payload; const state = getState().importSeries; const item = _.find(state.items, { id: name }) || { id: name, term, path, relativePath, isFetching: false, isPopulated: false, error: null }; dispatch(updateItem({ section, ...item, term, isQueued: true, items: [] })); const itemIndex = queue.indexOf(item.id); if (itemIndex >= 0) { queue.splice(itemIndex, 1); } if (topOfQueue) { queue.unshift(item.id); } else { queue.push(item.id); } if (term && term.length > 2) { dispatch(startLookupSeries({ start: true })); } }, [START_LOOKUP_SERIES]: function(getState, payload, dispatch) { if (concurrentLookups >= 1) { return; } const state = getState().importSeries; const { isLookingUpSeries, items } = state; const queueId = queue[0]; if (payload.start && !isLookingUpSeries) { dispatch(set({ section, isLookingUpSeries: true })); } else if (!isLookingUpSeries) { return; } else if (!queueId) { dispatch(set({ section, isLookingUpSeries: false })); return; } concurrentLookups++; queue.splice(0, 1); const queued = items.find((i) => i.id === queueId); dispatch(updateItem({ section, id: queued.id, isFetching: true })); const { request, abortRequest } = createAjaxRequest({ url: '/series/lookup', data: { term: queued.term } }); abortCurrentLookup = abortRequest; request.done((data) => { const selectedSeries = queued.selectedSeries || data[0]; const itemProps = { section, id: queued.id, isFetching: false, isPopulated: true, error: null, items: data, isQueued: false, selectedSeries, updateOnly: true }; if (selectedSeries && selectedSeries.seriesType !== seriesTypes.STANDARD) { itemProps.seriesType = selectedSeries.seriesType; } dispatch(updateItem(itemProps)); }); request.fail((xhr) => { dispatch(updateItem({ section, id: queued.id, isFetching: false, isPopulated: false, error: xhr, isQueued: false, updateOnly: true })); }); request.always(() => { concurrentLookups--; dispatch(startLookupSeries()); }); }, [LOOKUP_UNSEARCHED_SERIES]: function(getState, payload, dispatch) { const state = getState().importSeries; if (state.isLookingUpSeries) { return; } state.items.forEach((item) => { const id = item.id; if ( !item.isPopulated && !queue.includes(id) ) { queue.push(item.id); } }); if (queue.length) { dispatch(startLookupSeries({ start: true })); } }, [IMPORT_SERIES]: function(getState, payload, dispatch) { dispatch(set({ section, isImporting: true })); const ids = payload.ids; const items = getState().importSeries.items; const addedIds = []; const allNewSeries = ids.reduce((acc, id) => { const item = items.find((i) => i.id === id); const selectedSeries = item.selectedSeries; // Make sure we have a selected series and // the same series hasn't been added yet. if (selectedSeries && !acc.some((a) => a.tvdbId === selectedSeries.tvdbId)) { const newSeries = getNewSeries(_.cloneDeep(selectedSeries), item); newSeries.path = item.path; addedIds.push(id); acc.push(newSeries); } return acc; }, []); const promise = createAjaxRequest({ url: '/series/import', method: 'POST', contentType: 'application/json', data: JSON.stringify(allNewSeries) }).request; promise.done((data) => { dispatch(batchActions([ set({ section, isImporting: false, isImported: true, importError: null }), ...data.map((series) => updateItem({ section: 'series', ...series })), ...addedIds.map((id) => removeItem({ section, id })) ])); dispatch(fetchRootFolders()); }); promise.fail((xhr) => { dispatch(batchActions([ set({ section, isImporting: false, isImported: true, importError: xhr }), ...addedIds.map((id) => updateItem({ section, id })) ])); }); } }); // // Reducers export const reducers = createHandleActions({ [CANCEL_LOOKUP_SERIES]: function(state) { queue.splice(0, queue.length); const items = state.items.map((item) => { if (item.isQueued) { return { ...item, isQueued: false }; } return item; }); return Object.assign({}, state, { isLookingUpSeries: false, items }); }, [CLEAR_IMPORT_SERIES]: function(state) { if (abortCurrentLookup) { abortCurrentLookup(); abortCurrentLookup = null; } queue.splice(0, queue.length); return Object.assign({}, state, defaultState); }, [SET_IMPORT_SERIES_VALUE]: function(state, { payload }) { const newState = getSectionState(state, section); const items = newState.items; const index = items.findIndex((item) => item.id === payload.id); newState.items = [...items]; if (index >= 0) { const item = items[index]; newState.items.splice(index, 1, { ...item, ...payload }); } else { newState.items.push({ ...payload }); } return updateSectionState(state, section, newState); } }, defaultState, section);