mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-16 10:03:51 -07:00
New: Don't require artist mapping
This commit is contained in:
parent
1cc434a498
commit
be4e748977
159 changed files with 2934 additions and 4208 deletions
76
frontend/src/Store/Actions/Settings/rootFolders.js
Normal file
76
frontend/src/Store/Actions/Settings/rootFolders.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { createAction } from 'redux-actions';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
export const section = 'settings.rootFolders';
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_ROOT_FOLDERS = 'settings/rootFolders/fetchRootFolders';
|
||||
export const SET_ROOT_FOLDER_VALUE = 'settings/rootFolders/setRootFolderValue';
|
||||
export const SAVE_ROOT_FOLDER = 'settings/rootFolders/saveRootFolder';
|
||||
export const CANCEL_SAVE_ROOT_FOLDER = 'settings/rootFolders/cancelSaveRootFolder';
|
||||
export const DELETE_ROOT_FOLDER = 'settings/rootFolders/deleteRootFolder';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchRootFolders = createThunk(FETCH_ROOT_FOLDERS);
|
||||
export const saveRootFolder = createThunk(SAVE_ROOT_FOLDER);
|
||||
export const cancelSaveRootFolder = createThunk(CANCEL_SAVE_ROOT_FOLDER);
|
||||
export const deleteRootFolder = createThunk(DELETE_ROOT_FOLDER);
|
||||
|
||||
export const setRootFolderValue = createAction(SET_ROOT_FOLDER_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Details
|
||||
|
||||
export default {
|
||||
//
|
||||
// State
|
||||
|
||||
defaultState: {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
schema: {
|
||||
defaultTags: []
|
||||
},
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
items: [],
|
||||
pendingChanges: {}
|
||||
},
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
actionHandlers: {
|
||||
|
||||
[FETCH_ROOT_FOLDERS]: createFetchHandler(section, '/rootFolder'),
|
||||
|
||||
[SAVE_ROOT_FOLDER]: createSaveProviderHandler(section, '/rootFolder'),
|
||||
[CANCEL_SAVE_ROOT_FOLDER]: createCancelSaveProviderHandler(section),
|
||||
[DELETE_ROOT_FOLDER]: createRemoveItemHandler(section, '/rootFolder')
|
||||
|
||||
},
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
reducers: {
|
||||
[SET_ROOT_FOLDER_VALUE]: createSetSettingValueReducer(section)
|
||||
}
|
||||
};
|
|
@ -1,327 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
import getNewArtist from 'Utilities/Artist/getNewArtist';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import { set, removeItem, updateItem } from './baseActions';
|
||||
import { fetchRootFolders } from './rootFolderActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
export const section = 'importArtist';
|
||||
let concurrentLookups = 0;
|
||||
let abortCurrentLookup = null;
|
||||
const queue = [];
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
export const defaultState = {
|
||||
isLookingUpArtist: false,
|
||||
isImporting: false,
|
||||
isImported: false,
|
||||
importError: null,
|
||||
items: []
|
||||
};
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const QUEUE_LOOKUP_ARTIST = 'importArtist/queueLookupArtist';
|
||||
export const START_LOOKUP_ARTIST = 'importArtist/startLookupArtist';
|
||||
export const CANCEL_LOOKUP_ARTIST = 'importArtist/cancelLookupArtist';
|
||||
export const LOOKUP_UNSEARCHED_ARTIST = 'importArtist/lookupUnsearchedArtist';
|
||||
export const CLEAR_IMPORT_ARTIST = 'importArtist/clearImportArtist';
|
||||
export const SET_IMPORT_ARTIST_VALUE = 'importArtist/setImportArtistValue';
|
||||
export const IMPORT_ARTIST = 'importArtist/importArtist';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const queueLookupArtist = createThunk(QUEUE_LOOKUP_ARTIST);
|
||||
export const startLookupArtist = createThunk(START_LOOKUP_ARTIST);
|
||||
export const importArtist = createThunk(IMPORT_ARTIST);
|
||||
export const lookupUnsearchedArtist = createThunk(LOOKUP_UNSEARCHED_ARTIST);
|
||||
export const clearImportArtist = createAction(CLEAR_IMPORT_ARTIST);
|
||||
export const cancelLookupArtist = createAction(CANCEL_LOOKUP_ARTIST);
|
||||
|
||||
export const setImportArtistValue = createAction(SET_IMPORT_ARTIST_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
|
||||
[QUEUE_LOOKUP_ARTIST]: function(getState, payload, dispatch) {
|
||||
const {
|
||||
name,
|
||||
path,
|
||||
term,
|
||||
topOfQueue = false
|
||||
} = payload;
|
||||
|
||||
const state = getState().importArtist;
|
||||
const item = _.find(state.items, { id: name }) || {
|
||||
id: name,
|
||||
term,
|
||||
path,
|
||||
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(startLookupArtist({ start: true }));
|
||||
}
|
||||
},
|
||||
|
||||
[START_LOOKUP_ARTIST]: function(getState, payload, dispatch) {
|
||||
if (concurrentLookups >= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState().importArtist;
|
||||
|
||||
const {
|
||||
isLookingUpArtist,
|
||||
items
|
||||
} = state;
|
||||
|
||||
const queueId = queue[0];
|
||||
|
||||
if (payload.start && !isLookingUpArtist) {
|
||||
dispatch(set({ section, isLookingUpArtist: true }));
|
||||
} else if (!isLookingUpArtist) {
|
||||
return;
|
||||
} else if (!queueId) {
|
||||
dispatch(set({ section, isLookingUpArtist: 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: '/artist/lookup',
|
||||
data: {
|
||||
term: queued.term
|
||||
}
|
||||
});
|
||||
|
||||
abortCurrentLookup = abortRequest;
|
||||
|
||||
request.done((data) => {
|
||||
dispatch(updateItem({
|
||||
section,
|
||||
id: queued.id,
|
||||
isFetching: false,
|
||||
isPopulated: true,
|
||||
error: null,
|
||||
items: data,
|
||||
isQueued: false,
|
||||
selectedArtist: queued.selectedArtist || data[0],
|
||||
updateOnly: true
|
||||
}));
|
||||
});
|
||||
|
||||
request.fail((xhr) => {
|
||||
dispatch(updateItem({
|
||||
section,
|
||||
id: queued.id,
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: xhr,
|
||||
isQueued: false,
|
||||
updateOnly: true
|
||||
}));
|
||||
});
|
||||
|
||||
request.always(() => {
|
||||
concurrentLookups--;
|
||||
|
||||
dispatch(startLookupArtist());
|
||||
});
|
||||
},
|
||||
|
||||
[LOOKUP_UNSEARCHED_ARTIST]: function(getState, payload, dispatch) {
|
||||
const state = getState().importArtist;
|
||||
|
||||
if (state.isLookingUpArtist) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.items.forEach((item) => {
|
||||
const id = item.id;
|
||||
|
||||
if (
|
||||
!item.isPopulated &&
|
||||
!queue.includes(id)
|
||||
) {
|
||||
queue.push(item.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (queue.length) {
|
||||
dispatch(startLookupArtist({ start: true }));
|
||||
}
|
||||
},
|
||||
|
||||
[IMPORT_ARTIST]: function(getState, payload, dispatch) {
|
||||
dispatch(set({ section, isImporting: true }));
|
||||
|
||||
const ids = payload.ids;
|
||||
const items = getState().importArtist.items;
|
||||
const addedIds = [];
|
||||
|
||||
const allNewArtist = ids.reduce((acc, id) => {
|
||||
const item = _.find(items, { id });
|
||||
const selectedArtist = item.selectedArtist;
|
||||
|
||||
// Make sure we have a selected artist and
|
||||
// the same artist hasn't been added yet.
|
||||
if (selectedArtist && !_.some(acc, { foreignArtistId: selectedArtist.foreignArtistId })) {
|
||||
const newArtist = getNewArtist(_.cloneDeep(selectedArtist), item);
|
||||
newArtist.path = item.path;
|
||||
|
||||
addedIds.push(id);
|
||||
acc.push(newArtist);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/artist/import',
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(allNewArtist)
|
||||
}).request;
|
||||
|
||||
promise.done((data) => {
|
||||
dispatch(batchActions([
|
||||
set({
|
||||
section,
|
||||
isImporting: false,
|
||||
isImported: true
|
||||
}),
|
||||
|
||||
...data.map((artist) => updateItem({ section: 'artist', ...artist })),
|
||||
|
||||
...addedIds.map((id) => removeItem({ section, id }))
|
||||
]));
|
||||
|
||||
dispatch(fetchRootFolders());
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
dispatch(batchActions(
|
||||
set({
|
||||
section,
|
||||
isImporting: false,
|
||||
isImported: true
|
||||
}),
|
||||
|
||||
addedIds.map((id) => updateItem({
|
||||
section,
|
||||
id,
|
||||
importError: xhr
|
||||
}))
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
export const reducers = createHandleActions({
|
||||
|
||||
[CANCEL_LOOKUP_ARTIST]: 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, {
|
||||
isLookingUpArtist: false,
|
||||
items
|
||||
});
|
||||
},
|
||||
|
||||
[CLEAR_IMPORT_ARTIST]: function(state) {
|
||||
if (abortCurrentLookup) {
|
||||
abortCurrentLookup();
|
||||
|
||||
abortCurrentLookup = null;
|
||||
}
|
||||
|
||||
queue.splice(0, queue.length);
|
||||
|
||||
return Object.assign({}, state, defaultState);
|
||||
},
|
||||
|
||||
[SET_IMPORT_ARTIST_VALUE]: function(state, { payload }) {
|
||||
const newState = getSectionState(state, section);
|
||||
const items = newState.items;
|
||||
const index = _.findIndex(items, { 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);
|
|
@ -8,7 +8,6 @@ import * as albums from './albumActions';
|
|||
import * as trackFiles from './trackFileActions';
|
||||
import * as albumHistory from './albumHistoryActions';
|
||||
import * as history from './historyActions';
|
||||
import * as importArtist from './importArtistActions';
|
||||
import * as interactiveImportActions from './interactiveImportActions';
|
||||
import * as oAuth from './oAuthActions';
|
||||
import * as organizePreview from './organizePreviewActions';
|
||||
|
@ -17,7 +16,6 @@ import * as paths from './pathActions';
|
|||
import * as providerOptions from './providerOptionActions';
|
||||
import * as queue from './queueActions';
|
||||
import * as releases from './releaseActions';
|
||||
import * as rootFolders from './rootFolderActions';
|
||||
import * as albumStudio from './albumStudioActions';
|
||||
import * as artist from './artistActions';
|
||||
import * as artistEditor from './artistEditorActions';
|
||||
|
@ -41,7 +39,6 @@ export default [
|
|||
trackFiles,
|
||||
albumHistory,
|
||||
history,
|
||||
importArtist,
|
||||
interactiveImportActions,
|
||||
oAuth,
|
||||
organizePreview,
|
||||
|
@ -50,7 +47,6 @@ export default [
|
|||
providerOptions,
|
||||
queue,
|
||||
releases,
|
||||
rootFolders,
|
||||
albumStudio,
|
||||
artist,
|
||||
artistEditor,
|
||||
|
|
|
@ -34,10 +34,10 @@ export const defaultState = {
|
|||
recentFolders: [],
|
||||
importMode: 'move',
|
||||
sortPredicates: {
|
||||
relativePath: function(item, direction) {
|
||||
const relativePath = item.relativePath;
|
||||
path: function(item, direction) {
|
||||
const path = item.path;
|
||||
|
||||
return relativePath.toLowerCase();
|
||||
return path.toLowerCase();
|
||||
},
|
||||
|
||||
artist: function(item, direction) {
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
import { batchActions } from 'redux-batched-actions';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
|
||||
import { set, updateItem } from './baseActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
export const section = 'rootFolders';
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
export const defaultState = {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
items: []
|
||||
};
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_ROOT_FOLDERS = 'rootFolders/fetchRootFolders';
|
||||
export const ADD_ROOT_FOLDER = 'rootFolders/addRootFolder';
|
||||
export const DELETE_ROOT_FOLDER = 'rootFolders/deleteRootFolder';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchRootFolders = createThunk(FETCH_ROOT_FOLDERS);
|
||||
export const addRootFolder = createThunk(ADD_ROOT_FOLDER);
|
||||
export const deleteRootFolder = createThunk(DELETE_ROOT_FOLDER);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
|
||||
[FETCH_ROOT_FOLDERS]: createFetchHandler('rootFolders', '/rootFolder'),
|
||||
|
||||
[DELETE_ROOT_FOLDER]: createRemoveItemHandler(
|
||||
'rootFolders',
|
||||
'/rootFolder',
|
||||
(state) => state.rootFolders
|
||||
),
|
||||
|
||||
[ADD_ROOT_FOLDER]: function(getState, payload, dispatch) {
|
||||
const path = payload.path;
|
||||
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: true
|
||||
}));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/rootFolder',
|
||||
method: 'POST',
|
||||
data: JSON.stringify({ path }),
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
promise.done((data) => {
|
||||
dispatch(batchActions([
|
||||
updateItem({
|
||||
section,
|
||||
...data
|
||||
}),
|
||||
|
||||
set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: null
|
||||
})
|
||||
]));
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: xhr
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
export const reducers = createHandleActions({}, defaultState, section);
|
|
@ -20,6 +20,7 @@ import qualityDefinitions from './Settings/qualityDefinitions';
|
|||
import qualityProfiles from './Settings/qualityProfiles';
|
||||
import releaseProfiles from './Settings/releaseProfiles';
|
||||
import remotePathMappings from './Settings/remotePathMappings';
|
||||
import rootFolders from './Settings/rootFolders';
|
||||
import ui from './Settings/ui';
|
||||
|
||||
export * from './Settings/delayProfiles';
|
||||
|
@ -41,6 +42,7 @@ export * from './Settings/qualityDefinitions';
|
|||
export * from './Settings/qualityProfiles';
|
||||
export * from './Settings/releaseProfiles';
|
||||
export * from './Settings/remotePathMappings';
|
||||
export * from './Settings/rootFolders';
|
||||
export * from './Settings/ui';
|
||||
|
||||
//
|
||||
|
@ -73,6 +75,7 @@ export const defaultState = {
|
|||
qualityProfiles: qualityProfiles.defaultState,
|
||||
releaseProfiles: releaseProfiles.defaultState,
|
||||
remotePathMappings: remotePathMappings.defaultState,
|
||||
rootFolders: rootFolders.defaultState,
|
||||
ui: ui.defaultState
|
||||
};
|
||||
|
||||
|
@ -113,6 +116,7 @@ export const actionHandlers = handleThunks({
|
|||
...qualityProfiles.actionHandlers,
|
||||
...releaseProfiles.actionHandlers,
|
||||
...remotePathMappings.actionHandlers,
|
||||
...rootFolders.actionHandlers,
|
||||
...ui.actionHandlers
|
||||
});
|
||||
|
||||
|
@ -144,6 +148,7 @@ export const reducers = createHandleActions({
|
|||
...qualityProfiles.reducers,
|
||||
...releaseProfiles.reducers,
|
||||
...remotePathMappings.reducers,
|
||||
...rootFolders.reducers,
|
||||
...ui.reducers
|
||||
|
||||
}, defaultState, section);
|
||||
|
|
|
@ -45,11 +45,6 @@ export const defaultState = {
|
|||
label: 'Path',
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'relativePath',
|
||||
label: 'Relative Path',
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
label: 'Duration',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue