diff --git a/frontend/src/Author/AuthorBanner.js b/frontend/src/Author/AuthorBanner.js
index ef249926d..61708f829 100644
--- a/frontend/src/Author/AuthorBanner.js
+++ b/frontend/src/Author/AuthorBanner.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import AuthorImage from './AuthorImage';
-const bannerPlaceholder = 'data:image/png;base64,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';
+const bannerPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPcAAAD3AQMAAAD7QlAQAAAABlBMVEUnJychISEIs8G4AAAEFklEQVRYw+2YMdPOQBDH95KQDEVSMKOj1FFQJx9BQc0nkeuUvoLS+AQ6MQqlRu8xCjpUgsjK7iXz2t1LMsbo8i/ey5vfc3e7m7tk9+DQoUOHDpGe3bu7hS8BwJ117BoAOLfOb/Hf62s4EY1VNrcPVvjNua1WuJ/b8xqoeR3sqFkllx8+AYAra9PniDg1ydr07cT7FQMy6k7ycQMKgJr5F4BrhvI9ZA3xCDU8fJggs9gBXJ35acX8lil74CPmO5w1xhwoIMVFMQcqKCfynH3soLLuEfkB4O5TBArDPZlH05ZkYMxBigyJDEyseylHFjjK4CzPyS4IE3gTgIxuAyulHzbG/as0PYsifM24X8/TA19Vxn2efjagNwFoHE2/GDAKpm86HE2AfMrmLQbqADnI2bzFQPv8y7NlM7naORU+uid+62X4xJg0V6PC1+KfvvSghWMgnh0cVIArCO694Ib+qWR4HQ257F9oRxu+L2FpzK3h7D5vPwqA5k1OPOwA4iaAOYWnZM4XPhPYT3eWDXriX4sHROjpskF7cC2eBHfUdVjeDw6/4Uk9oHqEz18DH9se8IvgCdQDBS/oLUxcPcB24mnAv+jfXvCMOdwI9jNXDxiJp9w9DCd4Afgdz96fF5GGk3xSCFBHw+gF4PAz9SQCwE7K5UGculJHGuTdKPun+IYHrafAUPfPKJdP4OhL7ErDuf9jfnXn6Gu6+Kj654EPKQIG7iu5PMLacGPO7Qf0EOMvx3LhhRh/5l+GOsahnPkw4Mw7sXzLedzxV+DvscsMZ8X51W0Olp/+5P7qIPlLPMEWP+3z5G94rXinuen/RWzAbe6g7hVvRX/DO8FdjMPB9+O3yD5fwf1fc72+/jcfN/cHRPZPJva/7q/27z9zlPyVfL9Abrgv/oW/Nvyx5vL9rbl5f78R/I3iTnP7fRH83QjVDpfCb4Kr71uxz1FzkN9nxfX32XKVHyj+BfweV/mJkM5Pdnkpsc6PfK64BynDM8lTiU1+l+LPP2iLUJj8sj5z3uaXgMPZFDY/rQDHs/rLTRxMfkwx4mX4hPLjaza/TgIfI/l1xvl5y/wT5+dSCd8rmXf8W2/qgx5S5rRYvAMlri+Ic2MKME9FCdQT/wJ8Ga1vSnzE+Z3l06REJi7qI1VfOXw0xusrCPVZ+6aP12dFqO/qN6d4fZeF+rB804X6sInXl/lrT1vBFtAu1KcuCfWpi9e33VLfJjZAS33ckvlZpH4uedu2nOcWhleiPr9peLFT32fyfGD7fMGBlf/jfCLZOd8oIrw6q4/o2jogzlc2z2fAW8w2nwvd3eqp0YXxCcdiS1HzRC8fw2ezJjvHVtn2tPbhqnOzTgNp1/kdv6pV7ig4RQOruuDBCax1+94dOHTo0KFDk34DoJynpPus3GIAAAAASUVORK5CYII=';
function AuthorBanner(props) {
return (
diff --git a/frontend/src/Author/AuthorImage.js b/frontend/src/Author/AuthorImage.js
index e86a45faf..3900deb19 100644
--- a/frontend/src/Author/AuthorImage.js
+++ b/frontend/src/Author/AuthorImage.js
@@ -9,7 +9,7 @@ function findImage(images, coverType) {
function getUrl(image, coverType, size) {
if (image) {
// Remove protocol
- let url = image.url.replace(/^https?:/, '');
+ let url = image.url;
url = url.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
diff --git a/frontend/src/Author/Details/AuthorDetails.js b/frontend/src/Author/Details/AuthorDetails.js
index ff3b640e2..40bf4eb35 100644
--- a/frontend/src/Author/Details/AuthorDetails.js
+++ b/frontend/src/Author/Details/AuthorDetails.js
@@ -6,6 +6,7 @@ import TextTruncate from 'react-text-truncate';
import formatBytes from 'Utilities/Number/formatBytes';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
+import stripHtml from 'Utilities/String/stripHtml';
import { align, icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import fonts from 'Styles/Variables/fonts';
import HeartRating from 'Components/HeartRating';
@@ -166,7 +167,6 @@ class AuthorDetails extends Component {
overview,
links,
images,
- authorType,
alternateTitles,
tags,
isSaving,
@@ -206,7 +206,6 @@ class AuthorDetails extends Component {
} = this.state;
const continuing = status === 'continuing';
- const endedString = authorType === 'Person' ? 'Deceased' : 'Ended';
let bookFilesCountMessage = 'No book files';
@@ -458,7 +457,7 @@ class AuthorDetails extends Component {
/>
- {continuing ? 'Continuing' : endedString}
+ {continuing ? 'Continuing' : 'Deceased'}
@@ -515,7 +514,7 @@ class AuthorDetails extends Component {
]*>?/gm, '')}
+ text={stripHtml(overview)}
/>
@@ -697,9 +696,8 @@ AuthorDetails.propTypes = {
statistics: PropTypes.object.isRequired,
qualityProfileId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired,
- authorType: PropTypes.string,
status: PropTypes.string.isRequired,
- overview: PropTypes.string.isRequired,
+ overview: PropTypes.string,
links: PropTypes.arrayOf(PropTypes.object).isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired,
diff --git a/frontend/src/Author/Details/AuthorDetailsSeries.js b/frontend/src/Author/Details/AuthorDetailsSeries.js
index 78b6a0b18..3973505fc 100644
--- a/frontend/src/Author/Details/AuthorDetailsSeries.js
+++ b/frontend/src/Author/Details/AuthorDetailsSeries.js
@@ -226,7 +226,7 @@ AuthorDetailsSeries.propTypes = {
onSortPress: PropTypes.func.isRequired,
onMonitorBookPress: PropTypes.func.isRequired,
uiSettings: PropTypes.object.isRequired,
- authorMonitored: PropTypes.object.isRequired
+ authorMonitored: PropTypes.bool.isRequired
};
export default AuthorDetailsSeries;
diff --git a/frontend/src/Author/Details/BookRow.js b/frontend/src/Author/Details/BookRow.js
index 1bfe6b114..a0c6d85c0 100644
--- a/frontend/src/Author/Details/BookRow.js
+++ b/frontend/src/Author/Details/BookRow.js
@@ -73,7 +73,6 @@ class BookRow extends Component {
title,
position,
ratings,
- disambiguation,
isSaving,
authorMonitored,
titleSlug,
@@ -124,7 +123,6 @@ class BookRow extends Component {
);
@@ -208,7 +206,6 @@ BookRow.propTypes = {
title: PropTypes.string.isRequired,
position: PropTypes.string,
ratings: PropTypes.object.isRequired,
- disambiguation: PropTypes.string,
titleSlug: PropTypes.string.isRequired,
isSaving: PropTypes.bool,
authorMonitored: PropTypes.bool.isRequired,
diff --git a/frontend/src/Author/Index/Overview/AuthorIndexOverview.js b/frontend/src/Author/Index/Overview/AuthorIndexOverview.js
index c236d3a7f..81fb73a11 100644
--- a/frontend/src/Author/Index/Overview/AuthorIndexOverview.js
+++ b/frontend/src/Author/Index/Overview/AuthorIndexOverview.js
@@ -4,6 +4,7 @@ import TextTruncate from 'react-text-truncate';
import { icons } from 'Helpers/Props';
import dimensions from 'Styles/Variables/dimensions';
import fonts from 'Styles/Variables/fonts';
+import stripHtml from 'Utilities/String/stripHtml';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
@@ -113,7 +114,8 @@ class AuthorIndexOverview extends Component {
const elementStyle = {
width: `${posterWidth}px`,
- height: `${posterHeight}px`
+ height: `${posterHeight}px`,
+ objectFit: 'contain'
};
const contentHeight = getContentHeight(rowHeight, isSmallScreen);
@@ -203,7 +205,7 @@ class AuthorIndexOverview extends Component {
>
diff --git a/frontend/src/Author/Index/Posters/AuthorIndexPoster.js b/frontend/src/Author/Index/Posters/AuthorIndexPoster.js
index c97bc3240..23d5de42e 100644
--- a/frontend/src/Author/Index/Posters/AuthorIndexPoster.js
+++ b/frontend/src/Author/Index/Posters/AuthorIndexPoster.js
@@ -110,9 +110,9 @@ class AuthorIndexPoster extends Component {
const elementStyle = {
width: `${posterWidth}px`,
- height: `${posterHeight}px`
+ height: `${posterHeight}px`,
+ objectFit: 'contain'
};
- elementStyle.objectFit = 'contain';
return (
diff --git a/frontend/src/Author/Index/Table/AuthorIndexRow.js b/frontend/src/Author/Index/Table/AuthorIndexRow.js
index 66fe2ee39..476033217 100644
--- a/frontend/src/Author/Index/Table/AuthorIndexRow.js
+++ b/frontend/src/Author/Index/Table/AuthorIndexRow.js
@@ -82,7 +82,6 @@ class AuthorIndexRow extends Component {
status,
authorName,
titleSlug,
- authorType,
qualityProfile,
metadataProfile,
nextBook,
@@ -134,7 +133,6 @@ class AuthorIndexRow extends Component {
- {authorType}
-
- );
- }
-
if (name === 'qualityProfileId') {
return (
);
@@ -39,7 +36,6 @@ function AuthorStatusCell(props) {
AuthorStatusCell.propTypes = {
className: PropTypes.string.isRequired,
- authorType: PropTypes.string,
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
component: PropTypes.elementType
diff --git a/frontend/src/Book/BookCover.js b/frontend/src/Book/BookCover.js
index 22fde5ae4..2d27302d2 100644
--- a/frontend/src/Book/BookCover.js
+++ b/frontend/src/Book/BookCover.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import AuthorImage from 'Author/AuthorImage';
-const coverPlaceholder = 'data:image/png;base64,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';
+const coverPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPcAAAD3AQMAAAD7QlAQAAAABlBMVEUnJychISEIs8G4AAAEFklEQVRYw+2YMdPOQBDH95KQDEVSMKOj1FFQJx9BQc0nkeuUvoLS+AQ6MQqlRu8xCjpUgsjK7iXz2t1LMsbo8i/ey5vfc3e7m7tk9+DQoUOHDpGe3bu7hS8BwJ117BoAOLfOb/Hf62s4EY1VNrcPVvjNua1WuJ/b8xqoeR3sqFkllx8+AYAra9PniDg1ydr07cT7FQMy6k7ycQMKgJr5F4BrhvI9ZA3xCDU8fJggs9gBXJ35acX8lil74CPmO5w1xhwoIMVFMQcqKCfynH3soLLuEfkB4O5TBArDPZlH05ZkYMxBigyJDEyseylHFjjK4CzPyS4IE3gTgIxuAyulHzbG/as0PYsifM24X8/TA19Vxn2efjagNwFoHE2/GDAKpm86HE2AfMrmLQbqADnI2bzFQPv8y7NlM7naORU+uid+62X4xJg0V6PC1+KfvvSghWMgnh0cVIArCO694Ib+qWR4HQ257F9oRxu+L2FpzK3h7D5vPwqA5k1OPOwA4iaAOYWnZM4XPhPYT3eWDXriX4sHROjpskF7cC2eBHfUdVjeDw6/4Uk9oHqEz18DH9se8IvgCdQDBS/oLUxcPcB24mnAv+jfXvCMOdwI9jNXDxiJp9w9DCd4Afgdz96fF5GGk3xSCFBHw+gF4PAz9SQCwE7K5UGculJHGuTdKPun+IYHrafAUPfPKJdP4OhL7ErDuf9jfnXn6Gu6+Kj654EPKQIG7iu5PMLacGPO7Qf0EOMvx3LhhRh/5l+GOsahnPkw4Mw7sXzLedzxV+DvscsMZ8X51W0Olp/+5P7qIPlLPMEWP+3z5G94rXinuen/RWzAbe6g7hVvRX/DO8FdjMPB9+O3yD5fwf1fc72+/jcfN/cHRPZPJva/7q/27z9zlPyVfL9Abrgv/oW/Nvyx5vL9rbl5f78R/I3iTnP7fRH83QjVDpfCb4Kr71uxz1FzkN9nxfX32XKVHyj+BfweV/mJkM5Pdnkpsc6PfK64BynDM8lTiU1+l+LPP2iLUJj8sj5z3uaXgMPZFDY/rQDHs/rLTRxMfkwx4mX4hPLjaza/TgIfI/l1xvl5y/wT5+dSCd8rmXf8W2/qgx5S5rRYvAMlri+Ic2MKME9FCdQT/wJ8Ga1vSnzE+Z3l06REJi7qI1VfOXw0xusrCPVZ+6aP12dFqO/qN6d4fZeF+rB804X6sInXl/lrT1vBFtAu1KcuCfWpi9e33VLfJjZAS33ckvlZpH4uedu2nOcWhleiPr9peLFT32fyfGD7fMGBlf/jfCLZOd8oIrw6q4/o2jogzlc2z2fAW8w2nwvd3eqp0YXxCcdiS1HzRC8fw2ezJjvHVtn2tPbhqnOzTgNp1/kdv6pV7ig4RQOruuDBCax1+94dOHTo0KFDk34DoJynpPus3GIAAAAASUVORK5CYII=';
function BookCover(props) {
return (
diff --git a/frontend/src/Book/Details/BookDetails.js b/frontend/src/Book/Details/BookDetails.js
index 0cb83f59c..4bddffd2c 100644
--- a/frontend/src/Book/Details/BookDetails.js
+++ b/frontend/src/Book/Details/BookDetails.js
@@ -7,6 +7,7 @@ import TextTruncate from 'react-text-truncate';
import formatBytes from 'Utilities/Number/formatBytes';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
+import stripHtml from 'Utilities/String/stripHtml';
import { align, icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import fonts from 'Styles/Variables/fonts';
import HeartRating from 'Components/HeartRating';
@@ -18,6 +19,7 @@ import Tooltip from 'Components/Tooltip/Tooltip';
import BookCover from 'Book/BookCover';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
// import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector';
+import EditBookModalConnector from 'Book/Edit/EditBookModalConnector';
import DeleteBookModal from 'Book/Delete/DeleteBookModal';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
@@ -44,28 +46,6 @@ function getFanartUrl(images) {
}
}
-function formatDuration(timeSpan) {
- const duration = moment.duration(timeSpan);
- const hours = duration.get('hours');
- const minutes = duration.get('minutes');
- let hoursText = 'Hours';
- let minText = 'Minutes';
-
- if (minutes === 1) {
- minText = 'Minute';
- }
-
- if (hours === 0) {
- return `${minutes} ${minText}`;
- }
-
- if (hours === 1) {
- hoursText = 'Hour';
- }
-
- return `${hours} ${hoursText} ${minutes} ${minText}`;
-}
-
function getExpandedState(newState) {
return {
allExpanded: newState.allSelected,
@@ -85,6 +65,7 @@ class BookDetails extends Component {
this.state = {
isOrganizeModalOpen: false,
isRetagModalOpen: false,
+ isEditBookModalOpen: false,
isDeleteBookModalOpen: false,
allExpanded: false,
allCollapsed: false,
@@ -112,8 +93,17 @@ class BookDetails extends Component {
this.setState({ isRetagModalOpen: false });
}
+ onEditBookPress = () => {
+ this.setState({ isEditBookModalOpen: true });
+ }
+
+ onEditBookModalClose = () => {
+ this.setState({ isEditBookModalOpen: false });
+ }
+
onDeleteBookPress = () => {
this.setState({
+ isEditBookModalOpen: false,
isDeleteBookModalOpen: true
});
}
@@ -153,8 +143,7 @@ class BookDetails extends Component {
id,
titleSlug,
title,
- disambiguation,
- duration,
+ pageCount,
overview,
statistics = {},
monitored,
@@ -179,6 +168,7 @@ class BookDetails extends Component {
const {
isOrganizeModalOpen,
// isRetagModalOpen,
+ isEditBookModalOpen,
isDeleteBookModalOpen,
allExpanded,
allCollapsed,
@@ -222,6 +212,12 @@ class BookDetails extends Component {
+
+
- {title}{disambiguation ? ` (${disambiguation})` : ''}
+ {title}
+
@@ -306,9 +303,9 @@ class BookDetails extends Component {
{
- !!duration &&
+ !!pageCount &&
- {formatDuration(duration)}
+ {`${pageCount} pages`}
}
@@ -397,7 +394,7 @@ class BookDetails extends Component {
]*>?/gm, '')}
+ text={stripHtml(overview)}
/>
@@ -488,6 +485,14 @@ class BookDetails extends Component {
{/* onModalClose={this.onRetagModalClose} */}
{/* /> */}
+
+
+
+
+ );
+}
+
+EditBookModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default EditBookModal;
diff --git a/frontend/src/Book/Edit/EditBookModalConnector.js b/frontend/src/Book/Edit/EditBookModalConnector.js
new file mode 100644
index 000000000..3f861dc66
--- /dev/null
+++ b/frontend/src/Book/Edit/EditBookModalConnector.js
@@ -0,0 +1,39 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { clearPendingChanges } from 'Store/Actions/baseActions';
+import EditBookModal from './EditBookModal';
+
+const mapDispatchToProps = {
+ clearPendingChanges
+};
+
+class EditBookModalConnector extends Component {
+
+ //
+ // Listeners
+
+ onModalClose = () => {
+ this.props.clearPendingChanges({ section: 'books' });
+ this.props.onModalClose();
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+EditBookModalConnector.propTypes = {
+ onModalClose: PropTypes.func.isRequired,
+ clearPendingChanges: PropTypes.func.isRequired
+};
+
+export default connect(undefined, mapDispatchToProps)(EditBookModalConnector);
diff --git a/frontend/src/Book/Edit/EditBookModalContent.js b/frontend/src/Book/Edit/EditBookModalContent.js
new file mode 100644
index 000000000..97c21edc8
--- /dev/null
+++ b/frontend/src/Book/Edit/EditBookModalContent.js
@@ -0,0 +1,133 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { inputTypes } from 'Helpers/Props';
+import Button from 'Components/Link/Button';
+import SpinnerButton from 'Components/Link/SpinnerButton';
+import ModalContent from 'Components/Modal/ModalContent';
+import ModalHeader from 'Components/Modal/ModalHeader';
+import ModalBody from 'Components/Modal/ModalBody';
+import ModalFooter from 'Components/Modal/ModalFooter';
+import Form from 'Components/Form/Form';
+import FormGroup from 'Components/Form/FormGroup';
+import FormLabel from 'Components/Form/FormLabel';
+import FormInputGroup from 'Components/Form/FormInputGroup';
+
+class EditBookModalContent extends Component {
+
+ //
+ // Listeners
+
+ onSavePress = () => {
+ const {
+ onSavePress
+ } = this.props;
+
+ onSavePress(false);
+
+ }
+
+ //
+ // Render
+
+ render() {
+ const {
+ title,
+ authorName,
+ statistics,
+ item,
+ isSaving,
+ onInputChange,
+ onModalClose,
+ ...otherProps
+ } = this.props;
+
+ const {
+ monitored,
+ anyEditionOk,
+ editions
+ } = item;
+
+ const hasFile = statistics ? statistics.bookFileCount : 0;
+
+ return (
+
+
+ Edit - {authorName} - {title}
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
+
+ );
+ }
+}
+
+EditBookModalContent.propTypes = {
+ bookId: PropTypes.number.isRequired,
+ title: PropTypes.string.isRequired,
+ authorName: PropTypes.string.isRequired,
+ statistics: PropTypes.object.isRequired,
+ item: PropTypes.object.isRequired,
+ isSaving: PropTypes.bool.isRequired,
+ onInputChange: PropTypes.func.isRequired,
+ onSavePress: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default EditBookModalContent;
diff --git a/frontend/src/Book/Edit/EditBookModalContentConnector.js b/frontend/src/Book/Edit/EditBookModalContentConnector.js
new file mode 100644
index 000000000..39dc6b591
--- /dev/null
+++ b/frontend/src/Book/Edit/EditBookModalContentConnector.js
@@ -0,0 +1,98 @@
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import selectSettings from 'Store/Selectors/selectSettings';
+import createBookSelector from 'Store/Selectors/createBookSelector';
+import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
+import { setBookValue, saveBook } from 'Store/Actions/bookActions';
+import EditBookModalContent from './EditBookModalContent';
+
+function createMapStateToProps() {
+ return createSelector(
+ (state) => state.books,
+ createBookSelector(),
+ createAuthorSelector(),
+ (bookState, book, author) => {
+ const {
+ isSaving,
+ saveError,
+ pendingChanges
+ } = bookState;
+
+ const bookSettings = _.pick(book, [
+ 'monitored',
+ 'anyEditionOk',
+ 'editions'
+ ]);
+
+ const settings = selectSettings(bookSettings, pendingChanges, saveError);
+
+ return {
+ title: book.title,
+ authorName: author.authorName,
+ bookType: book.bookType,
+ statistics: book.statistics,
+ isSaving,
+ saveError,
+ item: settings.settings,
+ ...settings
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ dispatchSetBookValue: setBookValue,
+ dispatchSaveBook: saveBook
+};
+
+class EditBookModalContentConnector extends Component {
+
+ //
+ // Lifecycle
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
+ this.props.onModalClose();
+ }
+ }
+
+ //
+ // Listeners
+
+ onInputChange = ({ name, value }) => {
+ this.props.dispatchSetBookValue({ name, value });
+ }
+
+ onSavePress = () => {
+ this.props.dispatchSaveBook({
+ id: this.props.bookId
+ });
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+EditBookModalContentConnector.propTypes = {
+ bookId: PropTypes.number,
+ isSaving: PropTypes.bool.isRequired,
+ saveError: PropTypes.object,
+ dispatchSetBookValue: PropTypes.func.isRequired,
+ dispatchSaveBook: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(EditBookModalContentConnector);
diff --git a/frontend/src/Components/Form/BookEditionSelectInputConnector.js b/frontend/src/Components/Form/BookEditionSelectInputConnector.js
new file mode 100644
index 000000000..fecede0c8
--- /dev/null
+++ b/frontend/src/Components/Form/BookEditionSelectInputConnector.js
@@ -0,0 +1,93 @@
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import titleCase from 'Utilities/String/titleCase';
+import SelectInput from './SelectInput';
+
+function createMapStateToProps() {
+ return createSelector(
+ (state, { bookEditions }) => bookEditions,
+ (bookEditions) => {
+ const values = _.map(bookEditions.value, (bookEdition) => {
+
+ let value = `${bookEdition.title}`;
+
+ if (bookEdition.disambiguation) {
+ value = `${value} (${titleCase(bookEdition.disambiguation)})`;
+ }
+
+ const extras = [];
+ if (bookEdition.language) {
+ extras.push(bookEdition.language);
+ }
+ if (bookEdition.publisher) {
+ extras.push(bookEdition.publisher);
+ }
+ if (bookEdition.isbn13) {
+ extras.push(bookEdition.isbn13);
+ }
+ if (bookEdition.format) {
+ extras.push(bookEdition.format);
+ }
+ if (bookEdition.pageCount > 0) {
+ extras.push(`${bookEdition.pageCount}p`);
+ }
+
+ if (extras) {
+ value = `${value} [${extras.join(', ')}]`;
+ }
+
+ return {
+ key: bookEdition.foreignEditionId,
+ value
+ };
+ });
+
+ const sortedValues = _.orderBy(values, ['value']);
+
+ const value = _.find(bookEditions.value, { monitored: true }).foreignEditionId;
+
+ return {
+ values: sortedValues,
+ value
+ };
+ }
+ );
+}
+
+class BookEditionSelectInputConnector extends Component {
+
+ //
+ // Listeners
+
+ onChange = ({ name, value }) => {
+ const {
+ bookEditions
+ } = this.props;
+
+ const updatedEditions = _.map(bookEditions.value, (e) => ({ ...e, monitored: false }));
+ _.find(updatedEditions, { foreignEditionId: value }).monitored = true;
+
+ this.props.onChange({ name, value: updatedEditions });
+ }
+
+ render() {
+
+ return (
+
+ );
+ }
+}
+
+BookEditionSelectInputConnector.propTypes = {
+ name: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+ bookEditions: PropTypes.object
+};
+
+export default connect(createMapStateToProps)(BookEditionSelectInputConnector);
diff --git a/frontend/src/Components/Form/BookReleaseSelectInputConnector.js b/frontend/src/Components/Form/BookReleaseSelectInputConnector.js
deleted file mode 100644
index 47219acf6..000000000
--- a/frontend/src/Components/Form/BookReleaseSelectInputConnector.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import _ from 'lodash';
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-import { createSelector } from 'reselect';
-import titleCase from 'Utilities/String/titleCase';
-import SelectInput from './SelectInput';
-
-function createMapStateToProps() {
- return createSelector(
- (state, { bookReleases }) => bookReleases,
- (bookReleases) => {
- const values = _.map(bookReleases.value, (bookRelease) => {
-
- return {
- key: bookRelease.foreignReleaseId,
- value: `${bookRelease.title}` +
- `${bookRelease.disambiguation ? ' (' : ''}${titleCase(bookRelease.disambiguation)}${bookRelease.disambiguation ? ')' : ''}` +
- `, ${bookRelease.mediumCount} med, ${bookRelease.bookCount} books` +
- `${bookRelease.country.length > 0 ? ', ' : ''}${bookRelease.country}` +
- `${bookRelease.format ? ', [' : ''}${bookRelease.format}${bookRelease.format ? ']' : ''}`
- };
- });
-
- const sortedValues = _.orderBy(values, ['value']);
-
- const value = _.find(bookReleases.value, { monitored: true }).foreignReleaseId;
-
- return {
- values: sortedValues,
- value
- };
- }
- );
-}
-
-class BookReleaseSelectInputConnector extends Component {
-
- //
- // Listeners
-
- onChange = ({ name, value }) => {
- const {
- bookReleases
- } = this.props;
-
- const updatedReleases = _.map(bookReleases.value, (e) => ({ ...e, monitored: false }));
- _.find(updatedReleases, { foreignReleaseId: value }).monitored = true;
-
- this.props.onChange({ name, value: updatedReleases });
- }
-
- render() {
-
- return (
-
- );
- }
-}
-
-BookReleaseSelectInputConnector.propTypes = {
- name: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- bookReleases: PropTypes.object
-};
-
-export default connect(createMapStateToProps)(BookReleaseSelectInputConnector);
diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js
index 73552bef1..c4711fc31 100644
--- a/frontend/src/Components/Form/FormInputGroup.js
+++ b/frontend/src/Components/Form/FormInputGroup.js
@@ -15,7 +15,7 @@ import PasswordInput from './PasswordInput';
import PathInputConnector from './PathInputConnector';
import QualityProfileSelectInputConnector from './QualityProfileSelectInputConnector';
import MetadataProfileSelectInputConnector from './MetadataProfileSelectInputConnector';
-import BookReleaseSelectInputConnector from './BookReleaseSelectInputConnector';
+import BookEditionSelectInputConnector from './BookEditionSelectInputConnector';
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
import SeriesTypeSelectInput from './SeriesTypeSelectInput';
import EnhancedSelectInput from './EnhancedSelectInput';
@@ -66,8 +66,8 @@ function getComponent(type) {
case inputTypes.METADATA_PROFILE_SELECT:
return MetadataProfileSelectInputConnector;
- case inputTypes.BOOK_RELEASE_SELECT:
- return BookReleaseSelectInputConnector;
+ case inputTypes.BOOK_EDITION_SELECT:
+ return BookEditionSelectInputConnector;
case inputTypes.ROOT_FOLDER_SELECT:
return RootFolderSelectInputConnector;
diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js
index ae6ba08ee..f55bd3248 100644
--- a/frontend/src/Helpers/Props/inputTypes.js
+++ b/frontend/src/Helpers/Props/inputTypes.js
@@ -11,7 +11,7 @@ export const PASSWORD = 'password';
export const PATH = 'path';
export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const METADATA_PROFILE_SELECT = 'metadataProfileSelect';
-export const BOOK_RELEASE_SELECT = 'bookReleaseSelect';
+export const BOOK_EDITION_SELECT = 'bookEditionSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const SELECT = 'select';
export const SERIES_TYPE_SELECT = 'authorTypeSelect';
@@ -33,7 +33,7 @@ export const all = [
PATH,
QUALITY_PROFILE_SELECT,
METADATA_PROFILE_SELECT,
- BOOK_RELEASE_SELECT,
+ BOOK_EDITION_SELECT,
ROOT_FOLDER_SELECT,
SELECT,
SERIES_TYPE_SELECT,
diff --git a/frontend/src/InteractiveImport/Edition/SelectEditionModal.js b/frontend/src/InteractiveImport/Edition/SelectEditionModal.js
new file mode 100644
index 000000000..59a387490
--- /dev/null
+++ b/frontend/src/InteractiveImport/Edition/SelectEditionModal.js
@@ -0,0 +1,37 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import Modal from 'Components/Modal/Modal';
+import SelectEditionModalContentConnector from './SelectEditionModalContentConnector';
+
+class SelectEditionModal extends Component {
+
+ //
+ // Render
+
+ render() {
+ const {
+ isOpen,
+ onModalClose,
+ ...otherProps
+ } = this.props;
+
+ return (
+
+
+
+ );
+ }
+}
+
+SelectEditionModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default SelectEditionModal;
diff --git a/frontend/src/InteractiveImport/Edition/SelectEditionModalContent.css b/frontend/src/InteractiveImport/Edition/SelectEditionModalContent.css
new file mode 100644
index 000000000..54f67bb07
--- /dev/null
+++ b/frontend/src/InteractiveImport/Edition/SelectEditionModalContent.css
@@ -0,0 +1,18 @@
+.modalBody {
+ composes: modalBody from '~Components/Modal/ModalBody.css';
+
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: column;
+}
+
+.filterInput {
+ composes: input from '~Components/Form/TextInput.css';
+
+ flex: 0 0 auto;
+ margin-bottom: 20px;
+}
+
+.scroller {
+ flex: 1 1 auto;
+}
diff --git a/frontend/src/InteractiveImport/Edition/SelectEditionModalContent.js b/frontend/src/InteractiveImport/Edition/SelectEditionModalContent.js
new file mode 100644
index 000000000..e5146a0e9
--- /dev/null
+++ b/frontend/src/InteractiveImport/Edition/SelectEditionModalContent.js
@@ -0,0 +1,93 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import Button from 'Components/Link/Button';
+import { scrollDirections } from 'Helpers/Props';
+import ModalContent from 'Components/Modal/ModalContent';
+import ModalHeader from 'Components/Modal/ModalHeader';
+import ModalBody from 'Components/Modal/ModalBody';
+import ModalFooter from 'Components/Modal/ModalFooter';
+import Table from 'Components/Table/Table';
+import TableBody from 'Components/Table/TableBody';
+import SelectEditionRow from './SelectEditionRow';
+import Alert from 'Components/Alert';
+import styles from './SelectEditionModalContent.css';
+
+const columns = [
+ {
+ name: 'book',
+ label: 'Book',
+ isVisible: true
+ },
+ {
+ name: 'edition',
+ label: 'Edition',
+ isVisible: true
+ }
+];
+
+class SelectEditionModalContent extends Component {
+
+ //
+ // Render
+
+ render() {
+ const {
+ books,
+ onEditionSelect,
+ onModalClose,
+ ...otherProps
+ } = this.props;
+
+ return (
+
+
+ Manual Import - Select Edition
+
+
+
+
+ Overrriding an edition here will disable automatic edition selection for that book in future.
+
+
+
+
+ {
+ books.map((item) => {
+ return (
+
+ );
+ })
+ }
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+SelectEditionModalContent.propTypes = {
+ books: PropTypes.arrayOf(PropTypes.object).isRequired,
+ onEditionSelect: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default SelectEditionModalContent;
diff --git a/frontend/src/InteractiveImport/Edition/SelectEditionModalContentConnector.js b/frontend/src/InteractiveImport/Edition/SelectEditionModalContentConnector.js
new file mode 100644
index 000000000..d02682260
--- /dev/null
+++ b/frontend/src/InteractiveImport/Edition/SelectEditionModalContentConnector.js
@@ -0,0 +1,63 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import {
+ updateInteractiveImportItem,
+ saveInteractiveImportItem
+} from 'Store/Actions/interactiveImportActions';
+import SelectEditionModalContent from './SelectEditionModalContent';
+
+function createMapStateToProps() {
+ return {};
+}
+
+const mapDispatchToProps = {
+ updateInteractiveImportItem,
+ saveInteractiveImportItem
+};
+
+class SelectEditionModalContentConnector extends Component {
+
+ //
+ // Listeners
+
+ onEditionSelect = (bookId, editionId) => {
+ const ids = this.props.importIdsByBook[bookId];
+
+ ids.forEach((id) => {
+ this.props.updateInteractiveImportItem({
+ id,
+ editionId,
+ disableReleaseSwitching: true,
+ tracks: [],
+ rejections: []
+ });
+ });
+
+ this.props.saveInteractiveImportItem({ id: ids });
+
+ this.props.onModalClose(true);
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+SelectEditionModalContentConnector.propTypes = {
+ importIdsByBook: PropTypes.object.isRequired,
+ books: PropTypes.arrayOf(PropTypes.object).isRequired,
+ updateInteractiveImportItem: PropTypes.func.isRequired,
+ saveInteractiveImportItem: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(SelectEditionModalContentConnector);
diff --git a/frontend/src/InteractiveImport/Edition/SelectEditionRow.css b/frontend/src/InteractiveImport/Edition/SelectEditionRow.css
new file mode 100644
index 000000000..e78f0bc19
--- /dev/null
+++ b/frontend/src/InteractiveImport/Edition/SelectEditionRow.css
@@ -0,0 +1,3 @@
+.albumRow {
+ cursor: pointer;
+}
diff --git a/frontend/src/InteractiveImport/Edition/SelectEditionRow.js b/frontend/src/InteractiveImport/Edition/SelectEditionRow.js
new file mode 100644
index 000000000..1d2037a1f
--- /dev/null
+++ b/frontend/src/InteractiveImport/Edition/SelectEditionRow.js
@@ -0,0 +1,125 @@
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { inputTypes } from 'Helpers/Props';
+import TableRow from 'Components/Table/TableRow';
+import TableRowCell from 'Components/Table/Cells/TableRowCell';
+import FormInputGroup from 'Components/Form/FormInputGroup';
+import titleCase from 'Utilities/String/titleCase';
+
+class SelectEditionRow extends Component {
+
+ //
+ // Listeners
+
+ onInputChange = ({ name, value }) => {
+ this.props.onEditionSelect(parseInt(name), parseInt(value));
+ }
+
+ //
+ // Render
+
+ render() {
+ const {
+ id,
+ matchedEditionId,
+ title,
+ disambiguation,
+ editions,
+ columns
+ } = this.props;
+
+ const extendedTitle = disambiguation ? `${title} (${disambiguation})` : title;
+
+ const values = _.map(editions, (bookEdition) => {
+
+ let value = `${bookEdition.title}`;
+
+ if (bookEdition.disambiguation) {
+ value = `${value} (${titleCase(bookEdition.disambiguation)})`;
+ }
+
+ const extras = [];
+ if (bookEdition.language) {
+ extras.push(bookEdition.language);
+ }
+ if (bookEdition.publisher) {
+ extras.push(bookEdition.publisher);
+ }
+ if (bookEdition.isbn13) {
+ extras.push(bookEdition.isbn13);
+ }
+ if (bookEdition.format) {
+ extras.push(bookEdition.format);
+ }
+ if (bookEdition.pageCount > 0) {
+ extras.push(`${bookEdition.pageCount}p`);
+ }
+
+ if (extras) {
+ value = `${value} [${extras.join(', ')}]`;
+ }
+
+ return {
+ key: bookEdition.id,
+ value
+ };
+ });
+
+ const sortedValues = _.orderBy(values, ['value']);
+
+ return (
+
+ {
+ columns.map((column) => {
+ const {
+ name,
+ isVisible
+ } = column;
+
+ if (!isVisible) {
+ return null;
+ }
+
+ if (name === 'book') {
+ return (
+
+ {extendedTitle}
+
+ );
+ }
+
+ if (name === 'edition') {
+ return (
+
+
+
+ );
+ }
+
+ return null;
+ })
+ }
+
+
+ );
+ }
+}
+
+SelectEditionRow.propTypes = {
+ id: PropTypes.number.isRequired,
+ matchedEditionId: PropTypes.number.isRequired,
+ title: PropTypes.string.isRequired,
+ disambiguation: PropTypes.string.isRequired,
+ editions: PropTypes.arrayOf(PropTypes.object).isRequired,
+ onEditionSelect: PropTypes.func.isRequired,
+ columns: PropTypes.arrayOf(PropTypes.object).isRequired
+};
+
+export default SelectEditionRow;
diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js
index bd382a37e..e6090baf5 100644
--- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js
+++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js
@@ -23,6 +23,7 @@ import TableBody from 'Components/Table/TableBody';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectAuthorModal from 'InteractiveImport/Author/SelectAuthorModal';
import SelectBookModal from 'InteractiveImport/Book/SelectBookModal';
+import SelectEditionModal from 'InteractiveImport/Edition/SelectEditionModal';
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
import InteractiveImportRow from './InteractiveImportRow';
import styles from './InteractiveImportModalContent.css';
@@ -79,6 +80,7 @@ const importModeOptions = [
const SELECT = 'select';
const AUTHOR = 'author';
const BOOK = 'book';
+const EDITION = 'edition';
const QUALITY = 'quality';
const replaceExistingFilesOptions = {
@@ -112,7 +114,7 @@ class InteractiveImportModalContent extends Component {
const selectedItems = _.filter(this.props.items, (x) => _.includes(selectedIds, x.id));
const inconsistent = _(selectedItems)
- .map((x) => ({ bookId: x.book ? x.book.id : 0, releaseId: x.bookReleaseId }))
+ .map((x) => ({ bookId: x.book ? x.book.id : 0, releaseId: x.EditionId }))
.groupBy('bookId')
.mapValues((book) => _(book).groupBy((x) => x.releaseId).values().value().length)
.values()
@@ -273,6 +275,7 @@ class InteractiveImportModalContent extends Component {
const bulkSelectOptions = [
{ key: SELECT, value: 'Select...', disabled: true },
{ key: BOOK, value: 'Select Book' },
+ { key: EDITION, value: 'Select Edition' },
{ key: QUALITY, value: 'Select Quality' }
];
@@ -469,6 +472,13 @@ class InteractiveImportModalContent extends Component {
onModalClose={this.onSelectModalClose}
/>
+ x.album).groupBy((x) => x.book.id).mapValues((x) => x.map((y) => y.id)).value()}
+ books={_.chain(items).filter((x) => x.book).keyBy((x) => x.book.id).mapValues((x) => ({ matchedEditionId: x.editionId, book: x.book })).values().value()}
+ onModalClose={this.onSelectModalClose}
+ />
+
diff --git a/frontend/src/Search/Author/AddNewAuthorSearchResult.js b/frontend/src/Search/Author/AddNewAuthorSearchResult.js
index df837fe73..fd74959ec 100644
--- a/frontend/src/Search/Author/AddNewAuthorSearchResult.js
+++ b/frontend/src/Search/Author/AddNewAuthorSearchResult.js
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import TextTruncate from 'react-text-truncate';
import dimensions from 'Styles/Variables/dimensions';
import fonts from 'Styles/Variables/fonts';
+import stripHtml from 'Utilities/String/stripHtml';
import { icons, kinds, sizes } from 'Helpers/Props';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
@@ -69,12 +70,10 @@ class AddNewAuthorSearchResult extends Component {
render() {
const {
foreignAuthorId,
- goodreadsId,
titleSlug,
authorName,
year,
disambiguation,
- authorType,
status,
overview,
ratings,
@@ -89,7 +88,7 @@ class AddNewAuthorSearchResult extends Component {
const linkProps = isExistingAuthor ? { to: `/author/${titleSlug}` } : { onPress: this.onPress };
- const endedString = authorType === 'Person' ? 'Deceased' : 'Ended';
+ const endedString = 'Deceased';
const height = calculateHeight(230, isSmallScreen);
@@ -143,7 +142,7 @@ class AddNewAuthorSearchResult extends Component {
-
-
{
- authorType ?
+ ratings.votes > 0 ?
:
null
}
@@ -191,7 +186,7 @@ class AddNewAuthorSearchResult extends Component {
@@ -214,12 +209,10 @@ class AddNewAuthorSearchResult extends Component {
AddNewAuthorSearchResult.propTypes = {
foreignAuthorId: PropTypes.string.isRequired,
- goodreadsId: PropTypes.number.isRequired,
titleSlug: PropTypes.string.isRequired,
authorName: PropTypes.string.isRequired,
year: PropTypes.number,
disambiguation: PropTypes.string,
- authorType: PropTypes.string,
status: PropTypes.string.isRequired,
overview: PropTypes.string,
ratings: PropTypes.object.isRequired,
diff --git a/frontend/src/Search/Book/AddNewBookModalContent.js b/frontend/src/Search/Book/AddNewBookModalContent.js
index 8a612c10b..e540fe7c6 100644
--- a/frontend/src/Search/Book/AddNewBookModalContent.js
+++ b/frontend/src/Search/Book/AddNewBookModalContent.js
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import TextTruncate from 'react-text-truncate';
+import stripHtml from 'Utilities/String/stripHtml';
import { kinds } from 'Helpers/Props';
import SpinnerButton from 'Components/Link/SpinnerButton';
import CheckInput from 'Components/Form/CheckInput';
@@ -93,7 +94,7 @@ class AddNewBookModalContent extends Component {
:
null
diff --git a/frontend/src/Search/Book/AddNewBookSearchResult.js b/frontend/src/Search/Book/AddNewBookSearchResult.js
index ad7be4357..b58567fa4 100644
--- a/frontend/src/Search/Book/AddNewBookSearchResult.js
+++ b/frontend/src/Search/Book/AddNewBookSearchResult.js
@@ -4,6 +4,7 @@ import React, { Component } from 'react';
import TextTruncate from 'react-text-truncate';
import dimensions from 'Styles/Variables/dimensions';
import fonts from 'Styles/Variables/fonts';
+import stripHtml from 'Utilities/String/stripHtml';
import { icons, sizes } from 'Helpers/Props';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
@@ -70,7 +71,6 @@ class AddNewBookSearchResult extends Component {
render() {
const {
foreignBookId,
- goodreadsId,
titleSlug,
title,
releaseDate,
@@ -79,6 +79,7 @@ class AddNewBookSearchResult extends Component {
ratings,
images,
author,
+ editions,
isExistingBook,
isExistingAuthor,
isSmallScreen
@@ -132,7 +133,7 @@ class AddNewBookSearchResult extends Component {
@@ -209,7 +210,6 @@ class AddNewBookSearchResult extends Component {
AddNewBookSearchResult.propTypes = {
foreignBookId: PropTypes.string.isRequired,
- goodreadsId: PropTypes.number.isRequired,
titleSlug: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
releaseDate: PropTypes.string,
@@ -217,6 +217,7 @@ AddNewBookSearchResult.propTypes = {
overview: PropTypes.string,
ratings: PropTypes.object.isRequired,
author: PropTypes.object,
+ editions: PropTypes.arrayOf(PropTypes.object).isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingBook: PropTypes.bool.isRequired,
isExistingAuthor: PropTypes.bool.isRequired,
diff --git a/frontend/src/Settings/Profiles/Metadata/EditMetadataProfileModalContent.js b/frontend/src/Settings/Profiles/Metadata/EditMetadataProfileModalContent.js
index c486ce8f6..26df2a7f7 100644
--- a/frontend/src/Settings/Profiles/Metadata/EditMetadataProfileModalContent.js
+++ b/frontend/src/Settings/Profiles/Metadata/EditMetadataProfileModalContent.js
@@ -32,8 +32,7 @@ function EditMetadataProfileModalContent(props) {
const {
id,
name,
- minRating,
- minRatingCount,
+ minPopularity,
skipMissingDate,
skipMissingIsbn,
skipPartsAndSets,
@@ -73,27 +72,15 @@ function EditMetadataProfileModalContent(props) {
- Minimum Rating
+ Minimum Popularity
-
-
-
- Minimum Number of Ratings
-
-
diff --git a/frontend/src/Store/Actions/authorIndexActions.js b/frontend/src/Store/Actions/authorIndexActions.js
index ab984527a..b4d5579f8 100644
--- a/frontend/src/Store/Actions/authorIndexActions.js
+++ b/frontend/src/Store/Actions/authorIndexActions.js
@@ -73,12 +73,6 @@ export const defaultState = {
isVisible: true,
isModifiable: false
},
- {
- name: 'authorType',
- label: 'Type',
- isSortable: true,
- isVisible: true
- },
{
name: 'qualityProfileId',
label: 'Quality Profile',
diff --git a/frontend/src/Store/Actions/searchActions.js b/frontend/src/Store/Actions/searchActions.js
index 1b2128118..510c6ba0d 100644
--- a/frontend/src/Store/Actions/searchActions.js
+++ b/frontend/src/Store/Actions/searchActions.js
@@ -158,7 +158,7 @@ export const actionHandlers = handleThunks({
}).request;
promise.done((data) => {
- data.releases = itemToAdd.book.releases;
+ data.editions = itemToAdd.book.editions;
itemToAdd.book = data;
dispatch(batchActions([
updateItem({ section: 'authors', ...data.author }),
diff --git a/frontend/src/Utilities/String/stripHtml.js b/frontend/src/Utilities/String/stripHtml.js
new file mode 100644
index 000000000..c5dd964b4
--- /dev/null
+++ b/frontend/src/Utilities/String/stripHtml.js
@@ -0,0 +1,13 @@
+function stripHtml(html) {
+ if (!html) {
+ return html;
+ }
+
+ const fiddled = html.replace(/
/g, ' ');
+
+ const doc = new DOMParser().parseFromString(fiddled, 'text/html');
+ const text = doc.body.textContent || '';
+ return text.replace(/([;,.])([^\s.])/g, '$1 $2').replace(/\s{2,}/g, ' ').replace(/s+…/g, '…');
+}
+
+export default stripHtml;
diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs
index 23be73b75..08dd23811 100644
--- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs
+++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs
@@ -186,7 +186,8 @@ namespace NzbDrone.Common.Http.Dispatchers
webRequest.TransferEncoding = header.Value;
break;
case "User-Agent":
- throw new NotSupportedException("User-Agent other than Readarr not allowed.");
+ webRequest.UserAgent = header.Value;
+ break;
case "Proxy-Connection":
throw new NotImplementedException();
default:
diff --git a/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs b/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs
index e0ab1d989..ec3ec87b7 100644
--- a/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs
+++ b/src/NzbDrone.Core.Test/ArtistStatsTests/ArtistStatisticsFixture.cs
@@ -16,6 +16,7 @@ namespace NzbDrone.Core.Test.ArtistStatsTests
{
private Author _artist;
private Book _album;
+ private Edition _edition;
private BookFile _trackFile;
[SetUp]
@@ -32,10 +33,16 @@ namespace NzbDrone.Core.Test.ArtistStatsTests
.BuildNew();
Db.Insert(_album);
+ _edition = Builder.CreateNew()
+ .With(e => e.BookId = _album.Id)
+ .With(e => e.Monitored = true)
+ .BuildNew();
+ Db.Insert(_edition);
+
_trackFile = Builder.CreateNew()
.With(e => e.Author = _artist)
- .With(e => e.Book = _album)
- .With(e => e.BookId == _album.Id)
+ .With(e => e.Edition = _edition)
+ .With(e => e.EditionId == _edition.Id)
.With(e => e.Quality = new QualityModel(Quality.MP3_320))
.BuildNew();
}
diff --git a/src/NzbDrone.Core.Test/Datastore/LazyLoadingFixture.cs b/src/NzbDrone.Core.Test/Datastore/LazyLoadingFixture.cs
index 5afd1eabd..693dc0bec 100644
--- a/src/NzbDrone.Core.Test/Datastore/LazyLoadingFixture.cs
+++ b/src/NzbDrone.Core.Test/Datastore/LazyLoadingFixture.cs
@@ -51,10 +51,23 @@ namespace NzbDrone.Core.Test.Datastore
Db.InsertMany(albums);
+ var editions = new List();
+ foreach (var album in albums)
+ {
+ editions.Add(
+ Builder.CreateNew()
+ .With(v => v.Id = 0)
+ .With(v => v.BookId = album.Id)
+ .With(v => v.ForeignEditionId = "test" + album.Id)
+ .Build());
+ }
+
+ Db.InsertMany(editions);
+
var trackFiles = Builder.CreateListOfSize(1)
.All()
.With(v => v.Id = 0)
- .With(v => v.BookId = albums[0].Id)
+ .With(v => v.EditionId = editions[0].Id)
.With(v => v.Quality = new QualityModel())
.BuildListOfNew();
@@ -97,40 +110,15 @@ namespace NzbDrone.Core.Test.Datastore
var db = Mocker.Resolve();
var files = MediaFileRepository.Query(db,
new SqlBuilder()
- .Join((t, a) => t.BookId == a.Id)
+ .Join((t, a) => t.EditionId == a.Id)
+ .Join((e, b) => e.BookId == b.Id)
.Join((album, artist) => album.AuthorMetadataId == artist.AuthorMetadataId)
.Join((a, m) => a.AuthorMetadataId == m.Id));
Assert.IsNotEmpty(files);
foreach (var file in files)
{
- Assert.IsTrue(file.Book.IsLoaded);
- Assert.IsTrue(file.Author.IsLoaded);
- Assert.IsTrue(file.Author.Value.Metadata.IsLoaded);
- }
- }
-
- [Test]
- public void should_lazy_load_tracks_if_not_joined_to_trackfile()
- {
- var db = Mocker.Resolve();
- var files = db.QueryJoined(
- new SqlBuilder()
- .Join((t, a) => t.BookId == a.Id)
- .Join((album, artist) => album.AuthorMetadataId == artist.AuthorMetadataId)
- .Join((a, m) => a.AuthorMetadataId == m.Id),
- (file, album, artist, metadata) =>
- {
- file.Book = album;
- file.Author = artist;
- file.Author.Value.Metadata = metadata;
- return file;
- });
-
- Assert.IsNotEmpty(files);
- foreach (var file in files)
- {
- Assert.IsTrue(file.Book.IsLoaded);
+ Assert.IsTrue(file.Edition.IsLoaded);
Assert.IsTrue(file.Author.IsLoaded);
Assert.IsTrue(file.Author.Value.Metadata.IsLoaded);
}
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs
index a584a4263..d660ab54d 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_trackFiles = Builder.CreateListOfSize(3)
.All()
- .With(t => t.BookId = _albums.First().Id)
+ .With(t => t.EditionId = _albums.First().Id)
.BuildList();
Mocker.GetMock()
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs
index 953f3b35f..4914b4b6a 100644
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
Path = "/My.Artist.S01E01.mp3",
Quality = new QualityModel(Quality.FLAC, new Revision(version: 1)),
DateAdded = DateTime.Now,
- BookId = 1
+ EditionId = 1
};
_secondFile =
new BookFile
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
Path = "/My.Artist.S01E02.mp3",
Quality = new QualityModel(Quality.FLAC, new Revision(version: 1)),
DateAdded = DateTime.Now,
- BookId = 2
+ EditionId = 2
};
var singleAlbumList = new List { new Book { Id = 1 } };
diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBookFilesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBookFilesFixture.cs
index 02e3938ee..8844613ab 100644
--- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBookFilesFixture.cs
+++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupOrphanedBookFilesFixture.cs
@@ -18,12 +18,12 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
var trackFile = Builder.CreateNew()
.With(h => h.Quality = new QualityModel())
- .With(h => h.BookId = 1)
+ .With(h => h.EditionId = 1)
.BuildNew();
Db.Insert(trackFile);
Subject.Clean();
- AllStoredModels[0].BookId.Should().Be(0);
+ AllStoredModels[0].EditionId.Should().Be(0);
}
}
}
diff --git a/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs b/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs
index 5337c8ec7..c1898e7d5 100644
--- a/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs
@@ -43,7 +43,6 @@ namespace NzbDrone.Core.Test.ImportListTests
.Returns(x => Builder
.CreateListOfSize(1)
.TheFirst(1)
- .With(b => b.GoodreadsId = x)
.With(b => b.ForeignBookId = x.ToString())
.BuildList());
diff --git a/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs b/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs
index b1e2a8ad6..b4641be10 100644
--- a/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs
@@ -18,8 +18,9 @@ namespace NzbDrone.Core.Test.MediaCoverTests
[TestFixture]
public class MediaCoverServiceFixture : CoreTest
{
- private Author _artist;
- private Book _album;
+ private Author _author;
+ private Book _book;
+ private Edition _edition;
private HttpResponse _httpResponse;
[SetUp]
@@ -27,14 +28,20 @@ namespace NzbDrone.Core.Test.MediaCoverTests
{
Mocker.SetConstant(new AppFolderInfo(Mocker.Resolve()));
- _artist = Builder.CreateNew()
+ _author = Builder.CreateNew()
.With(v => v.Id = 2)
.With(v => v.Metadata.Value.Images = new List { new MediaCover.MediaCover(MediaCoverTypes.Poster, "") })
.Build();
- _album = Builder.CreateNew()
- .With(v => v.Id = 4)
+ _edition = Builder.CreateNew()
+ .With(v => v.Id = 8)
.With(v => v.Images = new List { new MediaCover.MediaCover(MediaCoverTypes.Cover, "") })
+ .With(v => v.Monitored = true)
+ .Build();
+
+ _book = Builder.CreateNew()
+ .With(v => v.Id = 4)
+ .With(v => v.Editions = new List { _edition })
.Build();
_httpResponse = new HttpResponse(null, new HttpHeader(), "");
@@ -110,7 +117,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests
Subject.ConvertToLocalUrls(6, MediaCoverEntity.Book, covers);
- covers.Single().Url.Should().Be("/MediaCover/Albums/6/disc" + extension + "?lastWrite=1234");
+ covers.Single().Url.Should().Be("/MediaCover/Books/6/disc" + extension + "?lastWrite=1234");
}
[TestCase(".png")]
@@ -140,13 +147,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
Mocker.GetMock()
.Setup(v => v.GetBooksByAuthor(It.IsAny()))
- .Returns(new List { _album });
+ .Returns(new List { _book });
Mocker.GetMock()
.Setup(v => v.FileExists(It.IsAny()))
.Returns(true);
- Subject.HandleAsync(new AuthorRefreshCompleteEvent(_artist));
+ Subject.HandleAsync(new AuthorRefreshCompleteEvent(_author));
Mocker.GetMock()
.Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2));
@@ -161,13 +168,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
Mocker.GetMock()
.Setup(v => v.GetBooksByAuthor(It.IsAny()))
- .Returns(new List { _album });
+ .Returns(new List { _book });
Mocker.GetMock()
.Setup(v => v.FileExists(It.IsAny()))
.Returns(false);
- Subject.HandleAsync(new AuthorRefreshCompleteEvent(_artist));
+ Subject.HandleAsync(new AuthorRefreshCompleteEvent(_author));
Mocker.GetMock()
.Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2));
@@ -186,13 +193,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
Mocker.GetMock()
.Setup(v => v.GetBooksByAuthor(It.IsAny()))
- .Returns(new List { _album });
+ .Returns(new List { _book });
Mocker.GetMock()
.Setup(v => v.GetFileSize(It.IsAny()))
.Returns(1000);
- Subject.HandleAsync(new AuthorRefreshCompleteEvent(_artist));
+ Subject.HandleAsync(new AuthorRefreshCompleteEvent(_author));
Mocker.GetMock()
.Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
@@ -211,13 +218,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
Mocker.GetMock()
.Setup(v => v.GetBooksByAuthor(It.IsAny()))
- .Returns(new List { _album });
+ .Returns(new List { _book });
Mocker.GetMock()
.Setup(v => v.GetFileSize(It.IsAny()))
.Returns(0);
- Subject.HandleAsync(new AuthorRefreshCompleteEvent(_artist));
+ Subject.HandleAsync(new AuthorRefreshCompleteEvent(_author));
Mocker.GetMock()
.Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2));
@@ -236,13 +243,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
Mocker.GetMock()
.Setup(v => v.GetBooksByAuthor(It.IsAny()))
- .Returns(new List { _album });
+ .Returns(new List { _book });
Mocker.GetMock()
.Setup(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()))
.Throws();
- Subject.HandleAsync(new AuthorRefreshCompleteEvent(_artist));
+ Subject.HandleAsync(new AuthorRefreshCompleteEvent(_author));
Mocker.GetMock()
.Verify(v => v.Resize(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2));
diff --git a/src/NzbDrone.Core.Test/MediaFiles/AudioTagServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/AudioTagServiceFixture.cs
index 12c382c81..bb68724e9 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/AudioTagServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/AudioTagServiceFixture.cs
@@ -315,8 +315,12 @@ namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
.With(x => x.Author = artist)
.Build();
- var file = Builder.CreateNew()
+ var edition = Builder.CreateNew()
.With(x => x.Book = album)
+ .Build();
+
+ var file = Builder.CreateNew()
+ .With(x => x.Edition = edition)
.With(x => x.Author = artist)
.Build();
diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedTracksFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedTracksFixture.cs
index 1da292901..2b944b28e 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedTracksFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedTracksFixture.cs
@@ -15,6 +15,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
+using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
@@ -43,6 +44,14 @@ namespace NzbDrone.Core.Test.MediaFiles
.With(e => e.Author = artist)
.Build();
+ var edition = Builder.CreateNew()
+ .With(e => e.Book = album)
+ .Build();
+
+ var rootFolder = Builder.CreateNew()
+ .With(r => r.IsCalibreLibrary = false)
+ .Build();
+
_rejectedDecisions.Add(new ImportDecision(new LocalBook(), new Rejection("Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalBook(), new Rejection("Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalBook(), new Rejection("Rejected!")));
@@ -52,6 +61,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
Author = artist,
Book = album,
+ Edition = edition,
Path = Path.Combine(artist.Path, "Alien Ant Farm - 01 - Pilot.mp3"),
Quality = new QualityModel(Quality.MP3_320),
FileTrackInfo = new ParsedTrackInfo
@@ -69,6 +79,10 @@ namespace NzbDrone.Core.Test.MediaFiles
Mocker.GetMock()
.Setup(s => s.GetFilesByBook(It.IsAny()))
.Returns(new List());
+
+ Mocker.GetMock()
+ .Setup(s => s.GetBestRootFolder(It.IsAny()))
+ .Returns(rootFolder);
}
[Test]
@@ -152,6 +166,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
Author = fileDecision.Item.Author,
Book = fileDecision.Item.Book,
+ Edition = fileDecision.Item.Edition,
Path = @"C:\Test\Music\Alien Ant Farm\Alien Ant Farm - 01 - Pilot.mp3".AsOsAgnostic(),
Quality = new QualityModel(Quality.MP3_320),
Size = 80.Megabytes()
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs
index 714e2dd62..4b0082946 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaFileRepositoryFixture.cs
@@ -16,6 +16,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
private Author _artist;
private Book _album;
+ private Edition _edition;
[SetUp]
public void Setup()
@@ -37,12 +38,20 @@ namespace NzbDrone.Core.Test.MediaFiles
.Build();
Db.Insert(_album);
+ _edition = Builder.CreateNew()
+ .With(a => a.Id = 0)
+ .With(a => a.BookId = _album.Id)
+ .Build();
+ Db.Insert(_edition);
+
var files = Builder.CreateListOfSize(10)
.All()
.With(c => c.Id = 0)
.With(c => c.Quality = new QualityModel(Quality.MP3_320))
.TheFirst(5)
- .With(c => c.BookId = _album.Id)
+ .With(c => c.EditionId = _edition.Id)
+ .TheRest()
+ .With(c => c.EditionId = 0)
.TheFirst(1)
.With(c => c.Path = @"C:\Test\Path\Artist\somefile1.flac".AsOsAgnostic())
.TheNext(1)
@@ -109,8 +118,8 @@ namespace NzbDrone.Core.Test.MediaFiles
var file = Subject.GetFileWithPath(@"C:\Test\Path\Artist\somefile2.flac".AsOsAgnostic());
file.Should().NotBeNull();
- file.Book.IsLoaded.Should().BeTrue();
- file.Book.Value.Should().NotBeNull();
+ file.Edition.IsLoaded.Should().BeTrue();
+ file.Edition.Value.Should().NotBeNull();
file.Author.IsLoaded.Should().BeTrue();
file.Author.Value.Should().NotBeNull();
}
@@ -122,7 +131,7 @@ namespace NzbDrone.Core.Test.MediaFiles
var files = Subject.GetFilesByBook(_album.Id);
VerifyEagerLoaded(files);
- files.Should().OnlyContain(c => c.BookId == _album.Id);
+ files.Should().OnlyContain(c => c.EditionId == _album.Id);
}
private void VerifyData()
@@ -136,8 +145,8 @@ namespace NzbDrone.Core.Test.MediaFiles
{
foreach (var file in files)
{
- file.Book.IsLoaded.Should().BeTrue();
- file.Book.Value.Should().NotBeNull();
+ file.Edition.IsLoaded.Should().BeTrue();
+ file.Edition.Value.Should().NotBeNull();
file.Author.IsLoaded.Should().BeTrue();
file.Author.Value.Should().NotBeNull();
file.Author.Value.Metadata.IsLoaded.Should().BeTrue();
@@ -149,8 +158,8 @@ namespace NzbDrone.Core.Test.MediaFiles
{
foreach (var file in files)
{
- file.Book.IsLoaded.Should().BeFalse();
- file.Book.Value.Should().BeNull();
+ file.Edition.IsLoaded.Should().BeFalse();
+ file.Edition.Value.Should().BeNull();
file.Author.IsLoaded.Should().BeFalse();
file.Author.Value.Should().BeNull();
}
@@ -162,7 +171,7 @@ namespace NzbDrone.Core.Test.MediaFiles
Db.Delete(_album);
Subject.DeleteFilesByBook(_album.Id);
- Db.All().Where(x => x.BookId == _album.Id).Should().HaveCount(0);
+ Db.All().Where(x => x.EditionId == _album.Id).Should().HaveCount(0);
}
}
}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTests/FilterFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTests/FilterFixture.cs
index c3bc653e0..80791e688 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTests/FilterFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTests/FilterFixture.cs
@@ -213,7 +213,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
Path = "C:\\file2.avi".AsOsAgnostic(),
Size = 10,
Modified = _lastWrite,
- Book = new LazyLoaded(null)
+ Edition = new LazyLoaded(null)
}
});
@@ -239,7 +239,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
Path = "C:\\file2.avi".AsOsAgnostic(),
Size = 10,
Modified = _lastWrite,
- Book = Builder.CreateNew().Build()
+ Edition = Builder.CreateNew().Build()
}
});
diff --git a/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTests/MediaFileServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTests/MediaFileServiceFixture.cs
index 55f561da5..652ef1449 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTests/MediaFileServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/MediaFileServiceTests/MediaFileServiceFixture.cs
@@ -24,9 +24,9 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackFileMovingServiceTests
_trackFiles = Builder.CreateListOfSize(3)
.TheFirst(2)
- .With(f => f.BookId = _album.Id)
+ .With(f => f.EditionId = _album.Id)
.TheNext(1)
- .With(f => f.BookId = 0)
+ .With(f => f.EditionId = 0)
.Build().ToList();
}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/TrackFileMovingServiceTests/MoveTrackFileFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/TrackFileMovingServiceTests/MoveTrackFileFixture.cs
index 69d4a64a2..770dc36d3 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/TrackFileMovingServiceTests/MoveTrackFileFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/TrackFileMovingServiceTests/MoveTrackFileFixture.cs
@@ -43,15 +43,15 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackFileMovingServiceTests
.Build();
Mocker.GetMock()
- .Setup(s => s.BuildBookFileName(It.IsAny(), It.IsAny(), It.IsAny(), null, null))
+ .Setup(s => s.BuildBookFileName(It.IsAny(), It.IsAny(), It.IsAny(), null, null))
.Returns("File Name");
Mocker.GetMock()
- .Setup(s => s.BuildBookFilePath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
+ .Setup(s => s.BuildBookFilePath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(@"C:\Test\Music\Artist\Album\File Name.mp3".AsOsAgnostic());
Mocker.GetMock()
- .Setup(s => s.BuildBookPath(It.IsAny(), It.IsAny()))
+ .Setup(s => s.BuildBookPath(It.IsAny()))
.Returns(@"C:\Test\Music\Artist\Album".AsOsAgnostic());
var rootFolder = @"C:\Test\Music\".AsOsAgnostic();
diff --git a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Aggregation/AggregateFilenameInfoFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Aggregation/AggregateFilenameInfoFixture.cs
index e908a708c..cc675949a 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Aggregation/AggregateFilenameInfoFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Aggregation/AggregateFilenameInfoFixture.cs
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Aggregation.Aggregators
[TestFixture]
public class AggregateFilenameInfoFixture : CoreTest
{
- private LocalAlbumRelease GivenTracks(List files, string root)
+ private LocalEdition GivenTracks(List files, string root)
{
var tracks = files.Select(x => new LocalBook
{
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Aggregation.Aggregators
TrackNumbers = new[] { 0 },
}
}).ToList();
- return new LocalAlbumRelease(tracks);
+ return new LocalEdition(tracks);
}
private void VerifyData(LocalBook track, string artist, string title, int trackNum, int disc)
diff --git a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Identification/IdentificationServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Identification/IdentificationServiceFixture.cs
index eebdd110a..3e60e29cb 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Identification/IdentificationServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Identification/IdentificationServiceFixture.cs
@@ -19,7 +19,7 @@ using NzbDrone.Core.MediaFiles.BookImport.Aggregation.Aggregators;
using NzbDrone.Core.MediaFiles.BookImport.Identification;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.MetadataSource;
-using NzbDrone.Core.MetadataSource.SkyHook;
+using NzbDrone.Core.MetadataSource.Goodreads;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Metadata;
@@ -32,7 +32,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
public class IdentificationServiceFixture : DbTest
{
private AuthorService _authorService;
- private AddArtistService _addAuthorService;
+ private AddAuthorService _addAuthorService;
private RefreshAuthorService _refreshArtistService;
private IdentificationService _Subject;
@@ -59,10 +59,10 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
Mocker.SetConstant(Mocker.Resolve());
Mocker.SetConstant(Mocker.Resolve());
- Mocker.SetConstant(Mocker.Resolve());
- Mocker.SetConstant(Mocker.Resolve());
+ Mocker.SetConstant(Mocker.Resolve());
+ Mocker.SetConstant(Mocker.Resolve());
- _addAuthorService = Mocker.Resolve();
+ _addAuthorService = Mocker.Resolve();
Mocker.SetConstant(Mocker.Resolve());
_refreshArtistService = Mocker.Resolve();
@@ -73,11 +73,11 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
Mocker.SetConstant(Mocker.Resolve());
// set up the augmenters
- List> aggregators = new List>
+ List> aggregators = new List>
{
Mocker.Resolve()
};
- Mocker.SetConstant>>(aggregators);
+ Mocker.SetConstant>>(aggregators);
Mocker.SetConstant(Mocker.Resolve());
_Subject = Mocker.Resolve();
diff --git a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Identification/MunkresFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Identification/MunkresFixture.cs
deleted file mode 100644
index 30fb2cde8..000000000
--- a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Identification/MunkresFixture.cs
+++ /dev/null
@@ -1,192 +0,0 @@
-using FluentAssertions;
-using NUnit.Framework;
-using NzbDrone.Core.MediaFiles.BookImport.Identification;
-using NzbDrone.Test.Common;
-
-namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
-{
- [TestFixture]
- public class MunkresFixture : TestBase
- {
- // 2d arrays don't play nicely with attributes
- public void RunTest(double[,] costMatrix, double expectedCost)
- {
- var m = new Munkres(costMatrix);
- m.Run();
- m.Cost.Should().Be(expectedCost);
- }
-
- [Test]
- public void MunkresSquareTest1()
- {
- var c = new double[,]
- {
- { 1, 2, 3 },
- { 2, 4, 6 },
- { 3, 6, 9 }
- };
-
- RunTest(c, 10);
- }
-
- [Test]
- public void MunkresSquareTest2()
- {
- var c = new double[,]
- {
- { 400, 150, 400 },
- { 400, 450, 600 },
- { 300, 225, 300 }
- };
-
- RunTest(c, 850);
- }
-
- [Test]
- public void MunkresSquareTest3()
- {
- var c = new double[,]
- {
- { 10, 10, 8 },
- { 9, 8, 1 },
- { 9, 7, 4 }
- };
-
- RunTest(c, 18);
- }
-
- [Test]
- public void MunkresSquareTest4()
- {
- var c = new double[,]
- {
- { 5, 9, 1 },
- { 10, 3, 2 },
- { 8, 7, 4 }
- };
-
- RunTest(c, 12);
- }
-
- [Test]
- public void MunkresSquareTest5()
- {
- var c = new double[,]
- {
- { 12, 26, 17, 0, 0 },
- { 49, 43, 36, 10, 5 },
- { 97, 9, 66, 34, 0 },
- { 52, 42, 19, 36, 0 },
- { 15, 93, 55, 80, 0 }
- };
-
- RunTest(c, 48);
- }
-
- [Test]
- public void Munkres5x5Test()
- {
- var c = new double[,]
- {
- { 12, 9, 27, 10, 23 },
- { 7, 13, 13, 30, 19 },
- { 25, 18, 26, 11, 26 },
- { 9, 28, 26, 23, 13 },
- { 16, 16, 24, 6, 9 }
- };
-
- RunTest(c, 51);
- }
-
- [Test]
- public void Munkres10x10Test()
- {
- var c = new double[,]
- {
- { 37, 34, 29, 26, 19, 8, 9, 23, 19, 29 },
- { 9, 28, 20, 8, 18, 20, 14, 33, 23, 14 },
- { 15, 26, 12, 28, 6, 17, 9, 13, 21, 7 },
- { 2, 8, 38, 36, 39, 5, 36, 2, 38, 27 },
- { 30, 3, 33, 16, 21, 39, 7, 23, 28, 36 },
- { 7, 5, 19, 22, 36, 36, 24, 19, 30, 2 },
- { 34, 20, 13, 36, 12, 33, 9, 10, 23, 5 },
- { 7, 37, 22, 39, 33, 39, 10, 3, 13, 26 },
- { 21, 25, 23, 39, 31, 37, 32, 33, 38, 1 },
- { 17, 34, 40, 10, 29, 37, 40, 3, 25, 3 }
- };
-
- RunTest(c, 66);
- }
-
- [Test]
- public void Munkres20x20Test()
- {
- var c = new double[,]
- {
- { 5, 4, 3, 9, 8, 9, 3, 5, 6, 9, 4, 10, 3, 5, 6, 6, 1, 8, 10, 2 },
- { 10, 9, 9, 2, 8, 3, 9, 9, 10, 1, 7, 10, 8, 4, 2, 1, 4, 8, 4, 8 },
- { 10, 4, 4, 3, 1, 3, 5, 10, 6, 8, 6, 8, 4, 10, 7, 2, 4, 5, 1, 8 },
- { 2, 1, 4, 2, 3, 9, 3, 4, 7, 3, 4, 1, 3, 2, 9, 8, 6, 5, 7, 8 },
- { 3, 4, 4, 1, 4, 10, 1, 2, 6, 4, 5, 10, 2, 2, 3, 9, 10, 9, 9, 10 },
- { 1, 10, 1, 8, 1, 3, 1, 7, 1, 1, 2, 1, 2, 6, 3, 3, 4, 4, 8, 6 },
- { 1, 8, 7, 10, 10, 3, 4, 6, 1, 6, 6, 4, 9, 6, 9, 6, 4, 5, 4, 7 },
- { 8, 10, 3, 9, 4, 9, 3, 3, 4, 6, 4, 2, 6, 7, 7, 4, 4, 3, 4, 7 },
- { 1, 3, 8, 2, 6, 9, 2, 7, 4, 8, 10, 8, 10, 5, 1, 3, 10, 10, 2, 9 },
- { 2, 4, 1, 9, 2, 9, 7, 8, 2, 1, 4, 10, 5, 2, 7, 6, 5, 7, 2, 6 },
- { 4, 5, 1, 4, 2, 3, 3, 4, 1, 8, 8, 2, 6, 9, 5, 9, 6, 3, 9, 3 },
- { 3, 1, 1, 8, 6, 8, 8, 7, 9, 3, 2, 1, 8, 2, 4, 7, 3, 1, 2, 4 },
- { 5, 9, 8, 6, 10, 4, 10, 3, 4, 10, 10, 10, 1, 7, 8, 8, 7, 7, 8, 8 },
- { 1, 4, 6, 1, 6, 1, 2, 10, 5, 10, 2, 6, 2, 4, 5, 5, 3, 5, 1, 5 },
- { 5, 6, 9, 10, 6, 6, 10, 6, 4, 1, 5, 3, 9, 5, 2, 10, 9, 9, 5, 1 },
- { 10, 9, 4, 6, 9, 5, 3, 7, 10, 1, 6, 8, 1, 1, 10, 9, 5, 7, 7, 5 },
- { 2, 6, 6, 6, 6, 2, 9, 4, 7, 5, 3, 2, 10, 3, 4, 5, 10, 9, 1, 7 },
- { 5, 2, 4, 9, 8, 4, 8, 2, 4, 1, 3, 7, 6, 8, 1, 6, 8, 8, 10, 10 },
- { 9, 6, 3, 1, 8, 5, 7, 8, 7, 2, 1, 8, 2, 8, 3, 7, 4, 8, 7, 7 },
- { 8, 4, 4, 9, 7, 10, 6, 2, 1, 5, 8, 5, 1, 1, 1, 9, 1, 3, 5, 3 }
- };
-
- RunTest(c, 22);
- }
-
- [Test]
- public void MunkresRectangularTest1()
- {
- var c = new double[,]
- {
- { 400, 150, 400, 1 },
- { 400, 450, 600, 2 },
- { 300, 225, 300, 3 }
- };
-
- RunTest(c, 452);
- }
-
- [Test]
- public void MunkresRectangularTest2()
- {
- var c = new double[,]
- {
- { 10, 10, 8, 11 },
- { 9, 8, 1, 1 },
- { 9, 7, 4, 10 }
- };
-
- RunTest(c, 15);
- }
-
- [Test]
- public void MunkresRectangularTest3()
- {
- var c = new double[,]
- {
- { 34, 26, 17, 12 },
- { 43, 43, 36, 10 },
- { 97, 47, 66, 34 },
- { 52, 42, 19, 36 },
- { 15, 93, 55, 80 }
- };
-
- RunTest(c, 70);
- }
- }
-}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs
index 3f25059e1..5f6ca677f 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs
@@ -28,18 +28,19 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
private LocalBook _localTrack;
private Author _artist;
private Book _album;
+ private Edition _edition;
private QualityModel _quality;
private IdentificationOverrides _idOverrides;
private ImportDecisionMakerConfig _idConfig;
- private Mock> _albumpass1;
- private Mock> _albumpass2;
- private Mock> _albumpass3;
+ private Mock> _albumpass1;
+ private Mock> _albumpass2;
+ private Mock> _albumpass3;
- private Mock> _albumfail1;
- private Mock> _albumfail2;
- private Mock> _albumfail3;
+ private Mock> _albumfail1;
+ private Mock> _albumfail2;
+ private Mock> _albumfail3;
private Mock> _pass1;
private Mock> _pass2;
@@ -52,13 +53,13 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
[SetUp]
public void Setup()
{
- _albumpass1 = new Mock>();
- _albumpass2 = new Mock>();
- _albumpass3 = new Mock>();
+ _albumpass1 = new Mock>();
+ _albumpass2 = new Mock>();
+ _albumpass3 = new Mock>();
- _albumfail1 = new Mock>();
- _albumfail2 = new Mock>();
- _albumfail3 = new Mock>();
+ _albumfail1 = new Mock>();
+ _albumfail2 = new Mock>();
+ _albumfail3 = new Mock>();
_pass1 = new Mock>();
_pass2 = new Mock>();
@@ -68,13 +69,13 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
_fail2 = new Mock>();
_fail3 = new Mock>();
- _albumpass1.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Accept());
- _albumpass2.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Accept());
- _albumpass3.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Accept());
+ _albumpass1.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Accept());
+ _albumpass2.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Accept());
+ _albumpass3.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Accept());
- _albumfail1.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Reject("_albumfail1"));
- _albumfail2.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Reject("_albumfail2"));
- _albumfail3.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Reject("_albumfail3"));
+ _albumfail1.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Reject("_albumfail1"));
+ _albumfail2.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Reject("_albumfail2"));
+ _albumfail3.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Reject("_albumfail3"));
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Accept());
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny())).Returns(Decision.Accept());
@@ -93,6 +94,10 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
.With(x => x.Author = _artist)
.Build();
+ _edition = Builder.CreateNew()
+ .With(x => x.Book = _album)
+ .Build();
+
_quality = new QualityModel(Quality.MP3_320);
_localTrack = new LocalBook
@@ -116,9 +121,9 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
.Setup(s => s.Identify(It.IsAny>(), It.IsAny(), It.IsAny()))
.Returns((List tracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) =>
{
- var ret = new LocalAlbumRelease(tracks);
- ret.Book = _album;
- return new List { ret };
+ var ret = new LocalEdition(tracks);
+ ret.Edition = _edition;
+ return new List { ret };
});
Mocker.GetMock()
@@ -164,12 +169,12 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
Subject.GetImportDecisions(_fileInfos, null, itemInfo, _idConfig);
- _albumfail1.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
- _albumfail2.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
- _albumfail3.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
- _albumpass1.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
- _albumpass2.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
- _albumpass3.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
+ _albumfail1.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
+ _albumfail2.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
+ _albumfail3.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
+ _albumpass1.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
+ _albumpass2.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
+ _albumpass3.Verify(c => c.IsSatisfiedBy(It.IsAny(), It.IsAny()), Times.Once());
}
[Test]
@@ -317,7 +322,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
.Setup(s => s.Identify(It.IsAny>(), It.IsAny(), It.IsAny()))
.Returns((List