mirror of
https://github.com/fergalmoran/Readarr.git
synced 2026-01-04 15:54:33 +00:00
New: Use Goodreads directly, allow multiple editions of a book (new DB required)
This commit is contained in:
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import AuthorImage from './AuthorImage';
|
||||
|
||||
const bannerPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA+gAAAC5AgMAAADG9/24AAAADFBMVEUyMjI7Ozs1NTU4ODjgOsZvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QkRBgAc5PUQ8QAAB7tJREFUeNrtnb1rHEsMwMdjArtrUrpf3uMg2cOl+5QpXWRvjQkXlyHVK8MVYXFl3F+/GAzrNfdSuXkQnH9ie/Oqw32aFM6bkTQfd85rHrwi0qjJJql+pxlJo9FISv0XqX8mKkmSJEmSJEmSJEmSJEmSJEmS5NeVYrh7HDqJ5DeY23kQB64/u7zW91amzgXqvRqjfGYvarnfxqncuaQlf72Zxv4gSOluudOfjRy1Hzh1L+nPj+KU3jh0MWr3Sp87dDFq98An/msmg3zPW/aFR6/vRaBPglML6Mci0H0g92MYfrjvRgJ5TrDvhuFyGIZv9NdTOev93dDry1Z59mM56/2hU8WZcefFjZgVT/Z90SlEV9VKio3HeKYZLLSGII6WPP+oBv3ZwkL3iE4JG/ZRjUYb16mAripUO/c4Hl3bd/juCF19FuHeJn6nK90VBh0s3SjBvcFW/wTrvfBa118EbHY8qmMOtmgdupoKOLTvAmOH1k059LKAX+Qra/TncExH4hcBXV3Zf/+Dv5Vb43cfod/wt3PLsN5VF6HDiudt56IbB23Ql5/oZ8A7CfZWbgF61sap62XdlOZTZ2rF3c7ltNW1+f7LuDez/uc6uDfO8dwkbPXcKF8vPS9sds527pC2utH0uCb0Jmz2t8wNvN3q2jj4ntDJna94m3g4sb7HH8Gue0RH4Je8z61g4GGr79Wz1qHjbi94m/jcH1IOccsj+lt/sOFr4m0EPz+3XyNueURvSmfn+Ebx1rctMvOxU8foqOwVa+9mfdvHDH+DdYQOxAesvZslvc/wo4vQZy6eY+vdrCVrut/AyTW9CWvO3E1ra4L6i5Fxoka7MDan45vTOmx2MPGc0QH52Tb6kTPxXNGtW5/Tnl+oGP2N/dstY8eeO2M+bqM3zvVxRT+gS8Vdl5/z6CaCzfx/c41o3pP2+030UzrAHDNGv8d4FvMVAf1I4c07V/QlnNsyG9TN23ID/ZjObnO+6CZmydS+y8oG9Dfk2Gd80WcW3Rv44e5bZOLtD8EUHbRq0V0F/DAMn8fQ2UUv2UayEMxplaFvM0G7QR/ufqBcmH/gG85Z9BM8rMO5bdiUDu4ceaLvUjqWfJveQm8hpvnKFv1jOLxUTtkoBWf0nIK5Cd6wO207dAznTlmjH+K630KvuKPbOHYffJsOexykx0iWJ7pNRf+ttEXvW1U49F7ZbJ26RHSe6ehn5NRGMPBVQAfP12EQf8QZPTMxXac30M1RxtaWXLBFn3j0eRsHNIBuCycLtqfWCQZrBcZ0W+gVhXts0RtEXyit4zDOoE/N/5yNzNF3oVB0A93I6ise7bijr1XwbYiurySg7xjfpp+g375mjj5D9HYD3fr6YvkKcxU80Q8duvVt2+gjob/ljX7yFH1a80dvDfpia8GXqp3Wr1XJXevljvFt2QZ6a6tJ2Gvd2Pa8Xuu2iNB7o++r+lUJlQas93oOl04be73Ut7y1Ts4tn/0EfaxZOzcKaXIsqNiI4QtE5x7N7Z08RZ9CKpb38cWs9V26b4sDWURnr3W9foq+gpM8a3S4V+rhKUCcqrBXTufMUxU2QWUTkFtZGls3pjgnqJ4FdB1lZAd8/qMEZGRtAlJvJqONb2syzuj2CuK+1YU5reg2CzGNyqq6fpOpku8VRI7lchX+7SIy8NdQSaKn3K8bsWJOlZGBX2Ed0bUIdBXftJpzG1h2vjetWFqgHXrhczTWtx2h8llXVTytI4FHnaes0bGMqNhC1+jUXmFMx7iCaq6qy4HqxR7dFfMUtc34LQCVDPppcM13Kie5RmTGJYNUKBraDL57xEKalS0UzTgXiiqn1X3fRxR0bBf6TLEvD4aKkslTdO5F4fQUIHseo5ugfrShewbWjvsriHxT65WAByAHVCT7u0fvoKDC+rZClZyf/eSAngUTj1pfCXjshU/8smDiPTr7J374sDPDfI1Hd4cX1g874TmvRc+30U8V9+e88Ig7U6V26Ofk21rzg1ScH3Hj033bTXIZab2iGG6PdWOaQzLx7cShQ0FFfdxqlfFu2DAhxw4vf5zWV3hYZ96m47l7u0+DMAgdu1Dxbs4St+Rx6MbAwzIveLfk8Y2Y9J5HP1tij3DmjZii9lujQy9GXO/M22/5pmvUVdWiV3RkYd50zbfaI7Vb9GmDD0C4t9qLGiy+JPTVKT4A4d5gMY866N4i+p9KuUM767aavpkqLnmDDl10S8W/mSq20O3I3n8x6AXufP4tdKlxcpkZ4HN43FZ0mT3GCmicTO2ysW2uVXmFWp/yb5cdN0kHrb/ADwFN0uPW+ICOt+0SWuPjkW2tPTr+CjcSphjiGIzWodPQGwljMGj4Se/Q0bfJGH4Sj7zx6DJG3tCgo4HQoYiukDHoKB5vZdCtb9MrIUM7DyK169Zu+mEUMtQsjLJrtT7vWl2IGWXnBxjaFwFnraQBhmFs5dDqi66SNLYyGlY6XMgaVip4RG00mHh2LWwwcRhHPVsJG0cdhpDProQNIQ+j52e30kbPa19B5T64H9Wfqn0mTelB7Y04pWMFfCQf5JDTjYOTuSClu5QUSa9EyU0gf5BF7iaP2zxdq6TJjUydgxTD3aPvm5wkSZIkSZIkSZIkSZIkSZIk+Z9kv/534U2+Uyf0XwP9H83PZBlAqkdjAAAAAElFTkSuQmCC';
|
||||
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 (
|
||||
|
||||
@@ -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`);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
/>
|
||||
|
||||
<span className={styles.qualityProfileName}>
|
||||
{continuing ? 'Continuing' : endedString}
|
||||
{continuing ? 'Continuing' : 'Deceased'}
|
||||
</span>
|
||||
</Label>
|
||||
|
||||
@@ -515,7 +514,7 @@ class AuthorDetails extends Component {
|
||||
<div className={styles.overview}>
|
||||
<TextTruncate
|
||||
line={Math.floor(125 / (defaultFontSize * lineHeight))}
|
||||
text={overview.replace(/<[^>]*>?/gm, '')}
|
||||
text={stripHtml(overview)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -73,7 +73,6 @@ class BookRow extends Component {
|
||||
title,
|
||||
position,
|
||||
ratings,
|
||||
disambiguation,
|
||||
isSaving,
|
||||
authorMonitored,
|
||||
titleSlug,
|
||||
@@ -124,7 +123,6 @@ class BookRow extends Component {
|
||||
<BookTitleLink
|
||||
titleSlug={titleSlug}
|
||||
title={title}
|
||||
disambiguation={disambiguation}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
>
|
||||
<TextTruncate
|
||||
line={Math.floor(overviewHeight / (defaultFontSize * lineHeight))}
|
||||
text={overview}
|
||||
text={stripHtml(overview)}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<div className={styles.container}>
|
||||
|
||||
@@ -82,7 +82,6 @@ class AuthorIndexRow extends Component {
|
||||
status,
|
||||
authorName,
|
||||
titleSlug,
|
||||
authorType,
|
||||
qualityProfile,
|
||||
metadataProfile,
|
||||
nextBook,
|
||||
@@ -134,7 +133,6 @@ class AuthorIndexRow extends Component {
|
||||
<AuthorStatusCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
authorType={authorType}
|
||||
monitored={monitored}
|
||||
status={status}
|
||||
component={VirtualTableRowCell}
|
||||
@@ -184,17 +182,6 @@ class AuthorIndexRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'authorType') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
>
|
||||
{authorType}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'qualityProfileId') {
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
@@ -421,7 +408,6 @@ AuthorIndexRow.propTypes = {
|
||||
status: PropTypes.string.isRequired,
|
||||
authorName: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
authorType: PropTypes.string,
|
||||
qualityProfile: PropTypes.object.isRequired,
|
||||
metadataProfile: PropTypes.object.isRequired,
|
||||
nextBook: PropTypes.object,
|
||||
|
||||
@@ -8,15 +8,12 @@ import styles from './AuthorStatusCell.css';
|
||||
function AuthorStatusCell(props) {
|
||||
const {
|
||||
className,
|
||||
authorType,
|
||||
monitored,
|
||||
status,
|
||||
component: Component,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const endedString = authorType === 'Person' ? 'Deceased' : 'Ended';
|
||||
|
||||
return (
|
||||
<Component
|
||||
className={className}
|
||||
@@ -31,7 +28,7 @@ function AuthorStatusCell(props) {
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={status === 'ended' ? icons.AUTHOR_ENDED : icons.AUTHOR_CONTINUING}
|
||||
title={status === 'ended' ? endedString : 'Continuing'}
|
||||
title={status === 'ended' ? 'Deceased' : 'Continuing'}
|
||||
/>
|
||||
</Component>
|
||||
);
|
||||
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import AuthorImage from 'Author/AuthorImage';
|
||||
|
||||
const coverPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPcAAAD3AgMAAAC84irAAAAADFBMVEUyMjI7Ozs1NTU4ODjgOsZvAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+EJEBIzDdm9OfoAAAbkSURBVGje7Zq9b9s4FMBZFgUkBR27C3cw0MromL1jxwyVZASB67G4qWPgoSAyBdm9CwECKCp8nbIccGj/Ce/BTUb3Lh3aI997pCjnTnyyt0JcIif5+ZHvPZLvQ0KMYxzjGMc4xjGOcYxjHOP4JUfSfP7RVPvSH3MYX/eC5aecxne1v+w95WebFs/rwVO/8+h8PnT6t3ln/DFQuJ06/SyHiX9pxa7o5/lewkuLDxLvhM8tPki8g07dU8Gnj5zGlw7P79n4pDVYi8/YuHO4n03z0z6XXDom4G3TXDdN840+LobN/W1Ty2slHD8bNvevlUgutLmTj4NmT3pf6mMGcJGth+gefaZsDCjB2Wj65wN8ZmnAGnE6eFieI1FvcEISLjIUr9hm+w7PFeHiE9t0E7dyIatE48odXTPu0j/A3BMnXf7NXDxudTxbE2VxMWVu+sfwf3i1ZMLiaQLf+iWIP4VtjtTzFhc35vfveZrb4nPt4R95ulu1cxeVh8Psw7rzbgWp8dWHyr83WJpbgjypjS5XeZnqRxmJNUd3MS1d6ue/tOn0WuayNd2CoTlaeqwnIVeOgcWHdHdMS9cSN1vCy3bxZwzFm6VL7QA14WTudVj1sFvf4ReZNSCO0IvwngXFV3hkFcriuPokrPrYbYxjVAHiZ24zLYIeP7/E4xZUgHiZWt29D9ptGemHR7mPo9B10HLGbucRfs/Ww2f2CD4L2u0+wofKwwvrd0XoqCmr38CAZa1d58LesEpvgqtN4MCR1mVj2nZWOiweVB/CAXuyi59Y1auA2eekg6Xw8Tfm013A8LFV8mYXL61ZF4Hb8Zx8d9vBtbdG7s99XvOOZlF38QVtmlkAv0ffxTOjxU/o5p8FvKbSszw2ik87+Iz23Lwf134RiWf2tG3xN2T4oh8vDO4U33z+5qnefFnR77OA2wheh2WfbJBHeI/XgtNJEaHdtJNrvPn8E8eV/kW/2xn8FDc77LemOyq4J1XvSbds7SZ3cAV+86UXP283TGaFUk4ZwmNyugne8FaqxdHtFkH8GNewg2cc3PjsM7CbbNdMwQJ47aL3mP5H308ar5XOn2nUwpx+4hrx/z+qn5DBNqD4rMUpWACnPwnhkfa9SnZwvX1MnHLVi08cPle+0wBuAsykd8dO0KkS9L0dPCO37MVLxJc6nPHdTeNT/ZeLDQN/DEFpBzc33Bfckhx8K1q7IS5vuPgjbTf5AL97zcALxFUHN76QrF7heTHru54RN3bbxTeEn4Xx04f4NOfhSuPLncmnQk3z1yLlSE8fabtFHVyZyIQlXes8zrdSJR5ea7k3+asUooXg2mO4oDprT/XdHpROhouL/8A3edBw5DYxBhYdn08Q53jd0elDfApHbHjL6Hk/pvvNd1rEWdLl9iG+hpMgiMMdVEM64B8X5nq6ZBwX5rCSeK/4uInJROiwetLi0jtpG0yJBPOkTVQXryEPKqMQbq6JeyUTvUOkilq/EVGmo5NIpP3XRIzhXIafrjzF30JUIqecKxIjOpF6il9jbHTLxjs3rN5voPH+GxbDA1m7GrM9a4zdTigdCUUXD2MSSEAXQRxDo2QHl2iwV+h7gchqLrLrhmKxH/Z6nqLUQD5AYSHWAEwk+Z1Ck1vEAmEhBaVtufDtj8Zmv6U+PQNBqbDf/szVR5XNvQteSAzRyeQhzgnIKR2Invq43gQb4+oRaJCTTcRd6RkzGXlJQe3vDq8gsDB2S0QaSoViwKNW9Sh9zUzEMA2MWtU7nJUGYhIa4bnjcLthgkkopMAGj3dxXgoMCbg+laTFL8luSn9pFkrAMf031cmVJz0jXzsKFm6OSfVqYnEILPKZDjeicPFhQoaHbMhKX+NmZ5Q+ntr8n5obhGPVKlx48cs+FteKP3MlswWv6CSPHK4Dmntm0ckreW0snmxKbsnLFdyo4mrwjLYJo+Dmyn0k3uDTEpMRTrnPKza+IHy9wGSEU2yMvSrvHeJ/Qt2UV+p0hVacvsah0psKXqEVy7y2tPu3xhM1oMxLReY00tAlJG9JFZktzCwyU4lbuqQ7U22VN1zi9gvsIP05PjAL7H55H/C6rREzyvu41bbS4VXb1OV0FLG1YVsa1J1gtzaosVJbHO3Gb6z4bR2H89s61FRqCIcgL+E3lfyWlsaN3eR6QDP0pSdeKqOEZjOgoda285SUl5W+Jga181wz0WQFF2poM7FtZTZKXlXZ0Fam10htroY3Ug9s43pN5OJ2jyZy28Iu1nu0sNsGenGzRwO9bd8Xd/u0793LA8Vmn5cHnPhiH+Gt+HIv4Ye+tnHoSyMHvrJy6Aszh76uc+DLQuLQV5XGMY5xjGMc4xjHOMYxjnH80uNfW99BeoyzJCoAAAAASUVORK5CYII=';
|
||||
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 (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label="Edit"
|
||||
iconName={icons.EDIT}
|
||||
onPress={this.onEditBookPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label="Delete"
|
||||
iconName={icons.DELETE}
|
||||
@@ -272,8 +268,9 @@ class BookDetails extends Component {
|
||||
</div>
|
||||
|
||||
<div className={styles.title}>
|
||||
{title}{disambiguation ? ` (${disambiguation})` : ''}
|
||||
{title}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={styles.bookNavigationButtons}>
|
||||
@@ -306,9 +303,9 @@ class BookDetails extends Component {
|
||||
<div className={styles.details}>
|
||||
<div>
|
||||
{
|
||||
!!duration &&
|
||||
!!pageCount &&
|
||||
<span className={styles.duration}>
|
||||
{formatDuration(duration)}
|
||||
{`${pageCount} pages`}
|
||||
</span>
|
||||
}
|
||||
|
||||
@@ -397,7 +394,7 @@ class BookDetails extends Component {
|
||||
<div className={styles.overview}>
|
||||
<TextTruncate
|
||||
line={Math.floor(125 / (defaultFontSize * lineHeight))}
|
||||
text={overview.replace(/<[^>]*>?/gm, '')}
|
||||
text={stripHtml(overview)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -488,6 +485,14 @@ class BookDetails extends Component {
|
||||
{/* onModalClose={this.onRetagModalClose} */}
|
||||
{/* /> */}
|
||||
|
||||
<EditBookModalConnector
|
||||
isOpen={isEditBookModalOpen}
|
||||
bookId={id}
|
||||
authorId={author.id}
|
||||
onModalClose={this.onEditBookModalClose}
|
||||
onDeleteAuthorPress={this.onDeleteBookPress}
|
||||
/>
|
||||
|
||||
<DeleteBookModal
|
||||
isOpen={isDeleteBookModalOpen}
|
||||
bookId={id}
|
||||
@@ -505,8 +510,7 @@ BookDetails.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
disambiguation: PropTypes.string,
|
||||
duration: PropTypes.number,
|
||||
pageCount: PropTypes.number,
|
||||
overview: PropTypes.string,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
|
||||
@@ -98,6 +98,10 @@ const mapDispatchToProps = {
|
||||
toggleBooksMonitored
|
||||
};
|
||||
|
||||
function getMonitoredEditions(props) {
|
||||
return _.map(_.filter(props.editions, { monitored: true }), 'id').sort();
|
||||
}
|
||||
|
||||
class BookDetailsConnector extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
@@ -106,10 +110,8 @@ class BookDetailsConnector extends Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// If the id has changed we need to clear the books
|
||||
// files and fetch from the server.
|
||||
|
||||
if (prevProps.id !== this.props.id) {
|
||||
if (!_.isEqual(getMonitoredEditions(prevProps), getMonitoredEditions(this.props)) ||
|
||||
(prevProps.anyReleaseOk === false && this.props.anyReleaseOk === true)) {
|
||||
this.unpopulate();
|
||||
this.populate();
|
||||
}
|
||||
|
||||
25
frontend/src/Book/Edit/EditBookModal.js
Normal file
25
frontend/src/Book/Edit/EditBookModal.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditBookModalContentConnector from './EditBookModalContentConnector';
|
||||
|
||||
function EditBookModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<EditBookModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
EditBookModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default EditBookModal;
|
||||
39
frontend/src/Book/Edit/EditBookModalConnector.js
Normal file
39
frontend/src/Book/Edit/EditBookModalConnector.js
Normal file
@@ -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 (
|
||||
<EditBookModal
|
||||
{...this.props}
|
||||
onModalClose={this.onModalClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditBookModalConnector.propTypes = {
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
clearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(undefined, mapDispatchToProps)(EditBookModalConnector);
|
||||
133
frontend/src/Book/Edit/EditBookModalContent.js
Normal file
133
frontend/src/Book/Edit/EditBookModalContent.js
Normal file
@@ -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 (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Edit - {authorName} - {title}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form
|
||||
{...otherProps}
|
||||
>
|
||||
<FormGroup>
|
||||
<FormLabel>Monitored</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="monitored"
|
||||
helpText="Readarr will search for and download book"
|
||||
{...monitored}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Automatically Switch Edition</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="anyEditionOk"
|
||||
helpText="Readarr will automatically switch to the edition best matching downloaded files"
|
||||
{...anyEditionOk}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Edition</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.BOOK_EDITION_SELECT}
|
||||
name="editions"
|
||||
helpText="Change edition for this book"
|
||||
isDisabled={anyEditionOk.value && hasFile}
|
||||
bookEditions={editions}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<SpinnerButton
|
||||
isSpinning={isSaving}
|
||||
onPress={this.onSavePress}
|
||||
>
|
||||
Save
|
||||
</SpinnerButton>
|
||||
</ModalFooter>
|
||||
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
98
frontend/src/Book/Edit/EditBookModalContentConnector.js
Normal file
98
frontend/src/Book/Edit/EditBookModalContentConnector.js
Normal file
@@ -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 (
|
||||
<EditBookModalContent
|
||||
{...this.props}
|
||||
onInputChange={this.onInputChange}
|
||||
onSavePress={this.onSavePress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -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 (
|
||||
<SelectInput
|
||||
{...this.props}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BookEditionSelectInputConnector.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
bookEditions: PropTypes.object
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(BookEditionSelectInputConnector);
|
||||
@@ -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 (
|
||||
<SelectInput
|
||||
{...this.props}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BookReleaseSelectInputConnector.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
bookReleases: PropTypes.object
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(BookReleaseSelectInputConnector);
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
37
frontend/src/InteractiveImport/Edition/SelectEditionModal.js
Normal file
37
frontend/src/InteractiveImport/Edition/SelectEditionModal.js
Normal file
@@ -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 (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<SelectEditionModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectEditionModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectEditionModal;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Manual Import - Select Edition
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody
|
||||
className={styles.modalBody}
|
||||
scrollDirection={scrollDirections.NONE}
|
||||
>
|
||||
<Alert>
|
||||
Overrriding an edition here will <b>disable automatic edition selection</b> for that book in future.
|
||||
</Alert>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
{...otherProps}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
books.map((item) => {
|
||||
return (
|
||||
<SelectEditionRow
|
||||
key={item.book.id}
|
||||
matchedEditionId={item.matchedEditionId}
|
||||
columns={columns}
|
||||
onEditionSelect={onEditionSelect}
|
||||
{...item.book}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectEditionModalContent.propTypes = {
|
||||
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onEditionSelect: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectEditionModalContent;
|
||||
@@ -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 (
|
||||
<SelectEditionModalContent
|
||||
{...this.props}
|
||||
onEditionSelect={this.onEditionSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -0,0 +1,3 @@
|
||||
.albumRow {
|
||||
cursor: pointer;
|
||||
}
|
||||
125
frontend/src/InteractiveImport/Edition/SelectEditionRow.js
Normal file
125
frontend/src/InteractiveImport/Edition/SelectEditionRow.js
Normal file
@@ -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 (
|
||||
<TableRow>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'book') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{extendedTitle}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'edition') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name={id.toString()}
|
||||
values={sortedValues}
|
||||
value={matchedEditionId}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
}
|
||||
</TableRow>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
<SelectEditionModal
|
||||
isOpen={selectModalOpen === EDITION}
|
||||
importIdsByBook={_.chain(items).filter((x) => 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}
|
||||
/>
|
||||
|
||||
<SelectQualityModal
|
||||
isOpen={selectModalOpen === QUALITY}
|
||||
ids={selectedIds}
|
||||
|
||||
@@ -128,6 +128,7 @@ class InteractiveImportModalContentConnector extends Component {
|
||||
const {
|
||||
author,
|
||||
book,
|
||||
editionId,
|
||||
quality,
|
||||
disableReleaseSwitching
|
||||
} = item;
|
||||
@@ -151,6 +152,7 @@ class InteractiveImportModalContentConnector extends Component {
|
||||
path: item.path,
|
||||
authorId: author.id,
|
||||
bookId: book.id,
|
||||
editionId,
|
||||
quality,
|
||||
downloadId: this.props.downloadId,
|
||||
disableReleaseSwitching
|
||||
|
||||
@@ -141,10 +141,11 @@ class AddNewItem extends Component {
|
||||
);
|
||||
} else if (item.book) {
|
||||
const book = item.book;
|
||||
const edition = book.editions[0];
|
||||
return (
|
||||
<AddNewBookSearchResultConnector
|
||||
key={item.id}
|
||||
isExistingBook={'id' in book && book.id !== 0}
|
||||
isExistingBook={'id' in edition && edition.id !== 0}
|
||||
isExistingAuthor={'id' in book.author && book.author.id !== 0}
|
||||
{...book}
|
||||
/>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
<Link
|
||||
className={styles.mbLink}
|
||||
to={`https://goodreads.com/author/show/${goodreadsId}`}
|
||||
to={`https://goodreads.com/author/show/${foreignAuthorId}`}
|
||||
onPress={this.onMBLinkPress}
|
||||
>
|
||||
<Icon
|
||||
@@ -155,17 +154,13 @@ class AddNewAuthorSearchResult extends Component {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{
|
||||
ratings.votes > 0 ?
|
||||
<Label size={sizes.LARGE}>
|
||||
<HeartRating
|
||||
rating={ratings.value}
|
||||
iconSize={13}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
{
|
||||
authorType ?
|
||||
<Label size={sizes.LARGE}>
|
||||
{authorType}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
@@ -191,7 +186,7 @@ class AddNewAuthorSearchResult extends Component {
|
||||
<TextTruncate
|
||||
truncateText="…"
|
||||
line={Math.floor(height / (defaultFontSize * lineHeight))}
|
||||
text={overview}
|
||||
text={stripHtml(overview)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
<TextTruncate
|
||||
truncateText="…"
|
||||
line={8}
|
||||
text={overview}
|
||||
text={stripHtml(overview)}
|
||||
/>
|
||||
</div> :
|
||||
null
|
||||
|
||||
@@ -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 {
|
||||
|
||||
<Link
|
||||
className={styles.mbLink}
|
||||
to={`https://goodreads.com/book/show/${goodreadsId}`}
|
||||
to={`https://goodreads.com/book/show/${editions[0].foreignEditionId}`}
|
||||
onPress={this.onMBLinkPress}
|
||||
>
|
||||
<Icon
|
||||
@@ -185,7 +186,7 @@ class AddNewBookSearchResult extends Component {
|
||||
<TextTruncate
|
||||
truncateText="…"
|
||||
line={Math.floor(height / (defaultFontSize * lineHeight))}
|
||||
text={overview}
|
||||
text={stripHtml(overview)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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,
|
||||
|
||||
@@ -32,8 +32,7 @@ function EditMetadataProfileModalContent(props) {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
minRating,
|
||||
minRatingCount,
|
||||
minPopularity,
|
||||
skipMissingDate,
|
||||
skipMissingIsbn,
|
||||
skipPartsAndSets,
|
||||
@@ -73,27 +72,15 @@ function EditMetadataProfileModalContent(props) {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Minimum Rating</FormLabel>
|
||||
<FormLabel>Minimum Popularity</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="minRating"
|
||||
{...minRating}
|
||||
name="minPopularity"
|
||||
{...minPopularity}
|
||||
helpText="Popularity is average rating * number of votes"
|
||||
isFloat={true}
|
||||
min={0}
|
||||
max={5}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Minimum Number of Ratings</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="minRatingCount"
|
||||
{...minRatingCount}
|
||||
min={0}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -73,12 +73,6 @@ export const defaultState = {
|
||||
isVisible: true,
|
||||
isModifiable: false
|
||||
},
|
||||
{
|
||||
name: 'authorType',
|
||||
label: 'Type',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'qualityProfileId',
|
||||
label: 'Quality Profile',
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
13
frontend/src/Utilities/String/stripHtml.js
Normal file
13
frontend/src/Utilities/String/stripHtml.js
Normal file
@@ -0,0 +1,13 @@
|
||||
function stripHtml(html) {
|
||||
if (!html) {
|
||||
return html;
|
||||
}
|
||||
|
||||
const fiddled = html.replace(/<br\/>/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;
|
||||
@@ -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:
|
||||
|
||||
@@ -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<Edition>.CreateNew()
|
||||
.With(e => e.BookId = _album.Id)
|
||||
.With(e => e.Monitored = true)
|
||||
.BuildNew();
|
||||
Db.Insert(_edition);
|
||||
|
||||
_trackFile = Builder<BookFile>.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();
|
||||
}
|
||||
|
||||
@@ -51,10 +51,23 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
|
||||
Db.InsertMany(albums);
|
||||
|
||||
var editions = new List<Edition>();
|
||||
foreach (var album in albums)
|
||||
{
|
||||
editions.Add(
|
||||
Builder<Edition>.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<BookFile>.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<IDatabase>();
|
||||
var files = MediaFileRepository.Query(db,
|
||||
new SqlBuilder()
|
||||
.Join<BookFile, Book>((t, a) => t.BookId == a.Id)
|
||||
.Join<BookFile, Edition>((t, a) => t.EditionId == a.Id)
|
||||
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||
.Join<Book, Author>((album, artist) => album.AuthorMetadataId == artist.AuthorMetadataId)
|
||||
.Join<Author, AuthorMetadata>((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<IDatabase>();
|
||||
var files = db.QueryJoined<BookFile, Book, Author, AuthorMetadata>(
|
||||
new SqlBuilder()
|
||||
.Join<BookFile, Book>((t, a) => t.BookId == a.Id)
|
||||
.Join<Book, Author>((album, artist) => album.AuthorMetadataId == artist.AuthorMetadataId)
|
||||
.Join<Author, AuthorMetadata>((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);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
|
||||
_trackFiles = Builder<BookFile>.CreateListOfSize(3)
|
||||
.All()
|
||||
.With(t => t.BookId = _albums.First().Id)
|
||||
.With(t => t.EditionId = _albums.First().Id)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
|
||||
@@ -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<Book> { new Book { Id = 1 } };
|
||||
|
||||
@@ -18,12 +18,12 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
{
|
||||
var trackFile = Builder<BookFile>.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ namespace NzbDrone.Core.Test.ImportListTests
|
||||
.Returns<int>(x => Builder<Book>
|
||||
.CreateListOfSize(1)
|
||||
.TheFirst(1)
|
||||
.With(b => b.GoodreadsId = x)
|
||||
.With(b => b.ForeignBookId = x.ToString())
|
||||
.BuildList());
|
||||
|
||||
|
||||
@@ -18,8 +18,9 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
||||
[TestFixture]
|
||||
public class MediaCoverServiceFixture : CoreTest<MediaCoverService>
|
||||
{
|
||||
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<IAppFolderInfo>(new AppFolderInfo(Mocker.Resolve<IStartupContext>()));
|
||||
|
||||
_artist = Builder<Author>.CreateNew()
|
||||
_author = Builder<Author>.CreateNew()
|
||||
.With(v => v.Id = 2)
|
||||
.With(v => v.Metadata.Value.Images = new List<MediaCover.MediaCover> { new MediaCover.MediaCover(MediaCoverTypes.Poster, "") })
|
||||
.Build();
|
||||
|
||||
_album = Builder<Book>.CreateNew()
|
||||
.With(v => v.Id = 4)
|
||||
_edition = Builder<Edition>.CreateNew()
|
||||
.With(v => v.Id = 8)
|
||||
.With(v => v.Images = new List<MediaCover.MediaCover> { new MediaCover.MediaCover(MediaCoverTypes.Cover, "") })
|
||||
.With(v => v.Monitored = true)
|
||||
.Build();
|
||||
|
||||
_book = Builder<Book>.CreateNew()
|
||||
.With(v => v.Id = 4)
|
||||
.With(v => v.Editions = new List<Edition> { _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<IBookService>()
|
||||
.Setup(v => v.GetBooksByAuthor(It.IsAny<int>()))
|
||||
.Returns(new List<Book> { _album });
|
||||
.Returns(new List<Book> { _book });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Subject.HandleAsync(new AuthorRefreshCompleteEvent(_artist));
|
||||
Subject.HandleAsync(new AuthorRefreshCompleteEvent(_author));
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(2));
|
||||
@@ -161,13 +168,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
||||
|
||||
Mocker.GetMock<IBookService>()
|
||||
.Setup(v => v.GetBooksByAuthor(It.IsAny<int>()))
|
||||
.Returns(new List<Book> { _album });
|
||||
.Returns(new List<Book> { _book });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns(false);
|
||||
|
||||
Subject.HandleAsync(new AuthorRefreshCompleteEvent(_artist));
|
||||
Subject.HandleAsync(new AuthorRefreshCompleteEvent(_author));
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(2));
|
||||
@@ -186,13 +193,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
||||
|
||||
Mocker.GetMock<IBookService>()
|
||||
.Setup(v => v.GetBooksByAuthor(It.IsAny<int>()))
|
||||
.Returns(new List<Book> { _album });
|
||||
.Returns(new List<Book> { _book });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.GetFileSize(It.IsAny<string>()))
|
||||
.Returns(1000);
|
||||
|
||||
Subject.HandleAsync(new AuthorRefreshCompleteEvent(_artist));
|
||||
Subject.HandleAsync(new AuthorRefreshCompleteEvent(_author));
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Never());
|
||||
@@ -211,13 +218,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
||||
|
||||
Mocker.GetMock<IBookService>()
|
||||
.Setup(v => v.GetBooksByAuthor(It.IsAny<int>()))
|
||||
.Returns(new List<Book> { _album });
|
||||
.Returns(new List<Book> { _book });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.GetFileSize(It.IsAny<string>()))
|
||||
.Returns(0);
|
||||
|
||||
Subject.HandleAsync(new AuthorRefreshCompleteEvent(_artist));
|
||||
Subject.HandleAsync(new AuthorRefreshCompleteEvent(_author));
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(2));
|
||||
@@ -236,13 +243,13 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
||||
|
||||
Mocker.GetMock<IBookService>()
|
||||
.Setup(v => v.GetBooksByAuthor(It.IsAny<int>()))
|
||||
.Returns(new List<Book> { _album });
|
||||
.Returns(new List<Book> { _book });
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Setup(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()))
|
||||
.Throws<ApplicationException>();
|
||||
|
||||
Subject.HandleAsync(new AuthorRefreshCompleteEvent(_artist));
|
||||
Subject.HandleAsync(new AuthorRefreshCompleteEvent(_author));
|
||||
|
||||
Mocker.GetMock<IImageResizer>()
|
||||
.Verify(v => v.Resize(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()), Times.Exactly(2));
|
||||
|
||||
@@ -315,8 +315,12 @@ namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
|
||||
.With(x => x.Author = artist)
|
||||
.Build();
|
||||
|
||||
var file = Builder<BookFile>.CreateNew()
|
||||
var edition = Builder<Edition>.CreateNew()
|
||||
.With(x => x.Book = album)
|
||||
.Build();
|
||||
|
||||
var file = Builder<BookFile>.CreateNew()
|
||||
.With(x => x.Edition = edition)
|
||||
.With(x => x.Author = artist)
|
||||
.Build();
|
||||
|
||||
|
||||
@@ -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<Edition>.CreateNew()
|
||||
.With(e => e.Book = album)
|
||||
.Build();
|
||||
|
||||
var rootFolder = Builder<RootFolder>.CreateNew()
|
||||
.With(r => r.IsCalibreLibrary = false)
|
||||
.Build();
|
||||
|
||||
_rejectedDecisions.Add(new ImportDecision<LocalBook>(new LocalBook(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision<LocalBook>(new LocalBook(), new Rejection("Rejected!")));
|
||||
_rejectedDecisions.Add(new ImportDecision<LocalBook>(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<IMediaFileService>()
|
||||
.Setup(s => s.GetFilesByBook(It.IsAny<int>()))
|
||||
.Returns(new List<BookFile>());
|
||||
|
||||
Mocker.GetMock<IRootFolderService>()
|
||||
.Setup(s => s.GetBestRootFolder(It.IsAny<string>()))
|
||||
.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()
|
||||
|
||||
@@ -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<Edition>.CreateNew()
|
||||
.With(a => a.Id = 0)
|
||||
.With(a => a.BookId = _album.Id)
|
||||
.Build();
|
||||
Db.Insert(_edition);
|
||||
|
||||
var files = Builder<BookFile>.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<BookFile>().Where(x => x.BookId == _album.Id).Should().HaveCount(0);
|
||||
Db.All<BookFile>().Where(x => x.EditionId == _album.Id).Should().HaveCount(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
||||
Path = "C:\\file2.avi".AsOsAgnostic(),
|
||||
Size = 10,
|
||||
Modified = _lastWrite,
|
||||
Book = new LazyLoaded<Book>(null)
|
||||
Edition = new LazyLoaded<Edition>(null)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
|
||||
Path = "C:\\file2.avi".AsOsAgnostic(),
|
||||
Size = 10,
|
||||
Modified = _lastWrite,
|
||||
Book = Builder<Book>.CreateNew().Build()
|
||||
Edition = Builder<Edition>.CreateNew().Build()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackFileMovingServiceTests
|
||||
|
||||
_trackFiles = Builder<BookFile>.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();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,15 +43,15 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackFileMovingServiceTests
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IBuildFileNames>()
|
||||
.Setup(s => s.BuildBookFileName(It.IsAny<Author>(), It.IsAny<Book>(), It.IsAny<BookFile>(), null, null))
|
||||
.Setup(s => s.BuildBookFileName(It.IsAny<Author>(), It.IsAny<Edition>(), It.IsAny<BookFile>(), null, null))
|
||||
.Returns("File Name");
|
||||
|
||||
Mocker.GetMock<IBuildFileNames>()
|
||||
.Setup(s => s.BuildBookFilePath(It.IsAny<Author>(), It.IsAny<Book>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Setup(s => s.BuildBookFilePath(It.IsAny<Author>(), It.IsAny<Edition>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Returns(@"C:\Test\Music\Artist\Album\File Name.mp3".AsOsAgnostic());
|
||||
|
||||
Mocker.GetMock<IBuildFileNames>()
|
||||
.Setup(s => s.BuildBookPath(It.IsAny<Author>(), It.IsAny<Book>()))
|
||||
.Setup(s => s.BuildBookPath(It.IsAny<Author>()))
|
||||
.Returns(@"C:\Test\Music\Artist\Album".AsOsAgnostic());
|
||||
|
||||
var rootFolder = @"C:\Test\Music\".AsOsAgnostic();
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Aggregation.Aggregators
|
||||
[TestFixture]
|
||||
public class AggregateFilenameInfoFixture : CoreTest<AggregateFilenameInfo>
|
||||
{
|
||||
private LocalAlbumRelease GivenTracks(List<string> files, string root)
|
||||
private LocalEdition GivenTracks(List<string> 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)
|
||||
|
||||
@@ -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<IMediaFileService>(Mocker.Resolve<MediaFileService>());
|
||||
|
||||
Mocker.SetConstant<IConfigService>(Mocker.Resolve<IConfigService>());
|
||||
Mocker.SetConstant<IProvideAuthorInfo>(Mocker.Resolve<SkyHookProxy>());
|
||||
Mocker.SetConstant<IProvideBookInfo>(Mocker.Resolve<SkyHookProxy>());
|
||||
Mocker.SetConstant<IProvideAuthorInfo>(Mocker.Resolve<GoodreadsProxy>());
|
||||
Mocker.SetConstant<IProvideBookInfo>(Mocker.Resolve<GoodreadsProxy>());
|
||||
|
||||
_addAuthorService = Mocker.Resolve<AddArtistService>();
|
||||
_addAuthorService = Mocker.Resolve<AddAuthorService>();
|
||||
|
||||
Mocker.SetConstant<IRefreshBookService>(Mocker.Resolve<RefreshBookService>());
|
||||
_refreshArtistService = Mocker.Resolve<RefreshAuthorService>();
|
||||
@@ -73,11 +73,11 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
|
||||
Mocker.SetConstant<ICandidateService>(Mocker.Resolve<CandidateService>());
|
||||
|
||||
// set up the augmenters
|
||||
List<IAggregate<LocalAlbumRelease>> aggregators = new List<IAggregate<LocalAlbumRelease>>
|
||||
List<IAggregate<LocalEdition>> aggregators = new List<IAggregate<LocalEdition>>
|
||||
{
|
||||
Mocker.Resolve<AggregateFilenameInfo>()
|
||||
};
|
||||
Mocker.SetConstant<IEnumerable<IAggregate<LocalAlbumRelease>>>(aggregators);
|
||||
Mocker.SetConstant<IEnumerable<IAggregate<LocalEdition>>>(aggregators);
|
||||
Mocker.SetConstant<IAugmentingService>(Mocker.Resolve<AugmentingService>());
|
||||
|
||||
_Subject = Mocker.Resolve<IdentificationService>();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumpass1;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumpass2;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumpass3;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalEdition>> _albumpass1;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalEdition>> _albumpass2;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalEdition>> _albumpass3;
|
||||
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumfail1;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumfail2;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>> _albumfail3;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalEdition>> _albumfail1;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalEdition>> _albumfail2;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalEdition>> _albumfail3;
|
||||
|
||||
private Mock<IImportDecisionEngineSpecification<LocalBook>> _pass1;
|
||||
private Mock<IImportDecisionEngineSpecification<LocalBook>> _pass2;
|
||||
@@ -52,13 +53,13 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_albumpass1 = new Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>>();
|
||||
_albumpass2 = new Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>>();
|
||||
_albumpass3 = new Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>>();
|
||||
_albumpass1 = new Mock<IImportDecisionEngineSpecification<LocalEdition>>();
|
||||
_albumpass2 = new Mock<IImportDecisionEngineSpecification<LocalEdition>>();
|
||||
_albumpass3 = new Mock<IImportDecisionEngineSpecification<LocalEdition>>();
|
||||
|
||||
_albumfail1 = new Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>>();
|
||||
_albumfail2 = new Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>>();
|
||||
_albumfail3 = new Mock<IImportDecisionEngineSpecification<LocalAlbumRelease>>();
|
||||
_albumfail1 = new Mock<IImportDecisionEngineSpecification<LocalEdition>>();
|
||||
_albumfail2 = new Mock<IImportDecisionEngineSpecification<LocalEdition>>();
|
||||
_albumfail3 = new Mock<IImportDecisionEngineSpecification<LocalEdition>>();
|
||||
|
||||
_pass1 = new Mock<IImportDecisionEngineSpecification<LocalBook>>();
|
||||
_pass2 = new Mock<IImportDecisionEngineSpecification<LocalBook>>();
|
||||
@@ -68,13 +69,13 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
|
||||
_fail2 = new Mock<IImportDecisionEngineSpecification<LocalBook>>();
|
||||
_fail3 = new Mock<IImportDecisionEngineSpecification<LocalBook>>();
|
||||
|
||||
_albumpass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_albumpass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_albumpass3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_albumpass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_albumpass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_albumpass3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
|
||||
_albumfail1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_albumfail1"));
|
||||
_albumfail2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_albumfail2"));
|
||||
_albumfail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_albumfail3"));
|
||||
_albumfail1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_albumfail1"));
|
||||
_albumfail2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_albumfail2"));
|
||||
_albumfail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_albumfail3"));
|
||||
|
||||
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalBook>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalBook>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
|
||||
@@ -93,6 +94,10 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
|
||||
.With(x => x.Author = _artist)
|
||||
.Build();
|
||||
|
||||
_edition = Builder<Edition>.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<List<LocalBook>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns((List<LocalBook> tracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) =>
|
||||
{
|
||||
var ret = new LocalAlbumRelease(tracks);
|
||||
ret.Book = _album;
|
||||
return new List<LocalAlbumRelease> { ret };
|
||||
var ret = new LocalEdition(tracks);
|
||||
ret.Edition = _edition;
|
||||
return new List<LocalEdition> { ret };
|
||||
});
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
@@ -164,12 +169,12 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
|
||||
|
||||
Subject.GetImportDecisions(_fileInfos, null, itemInfo, _idConfig);
|
||||
|
||||
_albumfail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumfail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumfail3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumpass1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumpass2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumpass3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalAlbumRelease>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumfail1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumfail2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumfail3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumpass1.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumpass2.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
_albumpass3.Verify(c => c.IsSatisfiedBy(It.IsAny<LocalEdition>(), It.IsAny<DownloadClientItem>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -317,7 +322,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
|
||||
.Setup(s => s.Identify(It.IsAny<List<LocalBook>>(), It.IsAny<IdentificationOverrides>(), It.IsAny<ImportDecisionMakerConfig>()))
|
||||
.Returns((List<LocalBook> tracks, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) =>
|
||||
{
|
||||
return new List<LocalAlbumRelease> { new LocalAlbumRelease(tracks) };
|
||||
return new List<LocalEdition> { new LocalEdition(tracks) };
|
||||
});
|
||||
|
||||
var decisions = Subject.GetImportDecisions(_fileInfos, _idOverrides, null, _idConfig);
|
||||
|
||||
@@ -5,14 +5,14 @@ using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Books;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.MetadataSource.SkyHook;
|
||||
using NzbDrone.Core.MetadataSource.Goodreads;
|
||||
using NzbDrone.Core.Profiles.Metadata;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.MetadataSource.SkyHook
|
||||
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
||||
{
|
||||
[TestFixture]
|
||||
public class SkyHookProxyFixture : CoreTest<SkyHookProxy>
|
||||
public class GoodreadsProxyFixture : CoreTest<GoodreadsProxy>
|
||||
{
|
||||
private MetadataProfile _metadataProfile;
|
||||
|
||||
@@ -32,8 +32,8 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
[TestCase("amzn1.gr.author.v1.qTrNu9-PIaaBj5gYRDmN4Q", "Terry Pratchett")]
|
||||
[TestCase("amzn1.gr.author.v1.afCyJgprpWE2xJU2_z3zTQ", "Robert Harris")]
|
||||
[TestCase("1654", "Terry Pratchett")]
|
||||
[TestCase("575", "Robert Harris")]
|
||||
public void should_be_able_to_get_author_detail(string mbId, string name)
|
||||
{
|
||||
var details = Subject.GetAuthorInfo(mbId);
|
||||
@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
|
||||
details.Name.Should().Be(name);
|
||||
}
|
||||
|
||||
[TestCase("amzn1.gr.book.v1.2rp8a0vJ8clGzMzZf61R9Q", "Guards! Guards!")]
|
||||
[TestCase("64216", "Guards! Guards!")]
|
||||
public void should_be_able_to_get_book_detail(string mbId, string name)
|
||||
{
|
||||
var details = Subject.GetBookInfo(mbId);
|
||||
@@ -75,9 +75,6 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
|
||||
author.Metadata.Value.Overview.Should().NotBeNullOrWhiteSpace();
|
||||
author.Metadata.Value.Images.Should().NotBeEmpty();
|
||||
author.ForeignAuthorId.Should().NotBeNullOrWhiteSpace();
|
||||
author.Books.IsLoaded.Should().BeTrue();
|
||||
author.Books.Value.Should().NotBeEmpty();
|
||||
author.Books.Value.Should().OnlyContain(x => x.CleanTitle != null);
|
||||
}
|
||||
|
||||
private void ValidateAlbums(List<Book> albums, bool idOnly = false)
|
||||
@@ -4,15 +4,15 @@ using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Books;
|
||||
using NzbDrone.Core.MetadataSource.SkyHook;
|
||||
using NzbDrone.Core.MetadataSource.Goodreads;
|
||||
using NzbDrone.Core.Profiles.Metadata;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MetadataSource.SkyHook
|
||||
namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
||||
{
|
||||
[TestFixture]
|
||||
public class SkyHookProxySearchFixture : CoreTest<SkyHookProxy>
|
||||
public class GoodreadsProxySearchFixture : CoreTest<GoodreadsProxy>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -45,10 +45,10 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
|
||||
}
|
||||
|
||||
[TestCase("Harry Potter and the sorcerer's stone", null, "Harry Potter and the Sorcerer's Stone")]
|
||||
[TestCase("readarr:3", null, "Harry Potter and the Sorcerer's Stone")]
|
||||
[TestCase("readarr: 3", null, "Harry Potter and the Sorcerer's Stone")]
|
||||
[TestCase("readarrid:3", null, "Harry Potter and the Sorcerer's Stone")]
|
||||
[TestCase("goodreads:3", null, "Harry Potter and the Sorcerer's Stone")]
|
||||
[TestCase("readarr:3", null, "Harry Potter and the Philosopher's Stone")]
|
||||
[TestCase("readarr: 3", null, "Harry Potter and the Philosopher's Stone")]
|
||||
[TestCase("readarrid:3", null, "Harry Potter and the Philosopher's Stone")]
|
||||
[TestCase("goodreads:3", null, "Harry Potter and the Philosopher's Stone")]
|
||||
[TestCase("asin:B0192CTMYG", null, "Harry Potter and the Sorcerer's Stone")]
|
||||
[TestCase("isbn:9780439554930", null, "Harry Potter and the Sorcerer's Stone")]
|
||||
public void successful_album_search(string title, string artist, string expected)
|
||||
@@ -54,11 +54,19 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||
.Returns<Author, NamingConfig>((c, n) => c.Name);
|
||||
}
|
||||
|
||||
private Book AlbumToAdd(string bookId, string authorId)
|
||||
private Book AlbumToAdd(string editionId, string bookId, string authorId)
|
||||
{
|
||||
return new Book
|
||||
{
|
||||
ForeignBookId = bookId,
|
||||
Editions = new List<Edition>
|
||||
{
|
||||
new Edition
|
||||
{
|
||||
ForeignEditionId = editionId,
|
||||
Monitored = true
|
||||
}
|
||||
},
|
||||
AuthorMetadata = new AuthorMetadata
|
||||
{
|
||||
ForeignAuthorId = authorId
|
||||
@@ -69,9 +77,9 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||
[Test]
|
||||
public void should_be_able_to_add_a_album_without_passing_in_name()
|
||||
{
|
||||
var newAlbum = AlbumToAdd("5537624c-3d2f-4f5c-8099-df916082c85c", "cc2c9c3c-b7bc-4b8b-84d8-4fbd8779e493");
|
||||
var newAlbum = AlbumToAdd("edition", "book", "author");
|
||||
|
||||
GivenValidAlbum(newAlbum.ForeignBookId);
|
||||
GivenValidAlbum("edition");
|
||||
GivenValidPath();
|
||||
|
||||
var album = Subject.AddBook(newAlbum);
|
||||
@@ -82,11 +90,11 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||
[Test]
|
||||
public void should_throw_if_album_cannot_be_found()
|
||||
{
|
||||
var newAlbum = AlbumToAdd("5537624c-3d2f-4f5c-8099-df916082c85c", "cc2c9c3c-b7bc-4b8b-84d8-4fbd8779e493");
|
||||
var newAlbum = AlbumToAdd("edition", "book", "author");
|
||||
|
||||
Mocker.GetMock<IProvideBookInfo>()
|
||||
.Setup(s => s.GetBookInfo(newAlbum.ForeignBookId))
|
||||
.Throws(new BookNotFoundException(newAlbum.ForeignBookId));
|
||||
.Setup(s => s.GetBookInfo("edition"))
|
||||
.Throws(new BookNotFoundException("edition"));
|
||||
|
||||
Assert.Throws<ValidationException>(() => Subject.AddBook(newAlbum));
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ using NzbDrone.Test.Common;
|
||||
namespace NzbDrone.Core.Test.MusicTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class AddArtistFixture : CoreTest<AddArtistService>
|
||||
public class AddArtistFixture : CoreTest<AddAuthorService>
|
||||
{
|
||||
private Author _fakeArtist;
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
||||
{
|
||||
Title = "ANThology",
|
||||
ForeignBookId = "1",
|
||||
ForeignWorkId = "1",
|
||||
TitleSlug = "1-ANThology",
|
||||
CleanTitle = "anthology",
|
||||
Author = _artist,
|
||||
@@ -50,7 +49,6 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
||||
{
|
||||
Title = "+",
|
||||
ForeignBookId = "2",
|
||||
ForeignWorkId = "2",
|
||||
TitleSlug = "2-_",
|
||||
CleanTitle = "",
|
||||
Author = _artist,
|
||||
|
||||
@@ -143,6 +143,59 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||
item1.Should().Be(item2);
|
||||
}
|
||||
|
||||
private Edition GivenEdition()
|
||||
{
|
||||
return _fixture.Build<Edition>()
|
||||
.Without(x => x.Book)
|
||||
.Without(x => x.BookFiles)
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void two_equivalent_editions_should_be_equal()
|
||||
{
|
||||
var item1 = GivenEdition();
|
||||
var item2 = item1.JsonClone();
|
||||
|
||||
item1.Should().NotBeSameAs(item2);
|
||||
item1.Should().Be(item2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(typeof(EqualityPropertySource<Edition>), "TestCases")]
|
||||
public void two_different_editions_should_not_be_equal(PropertyInfo prop)
|
||||
{
|
||||
var item1 = GivenEdition();
|
||||
var item2 = item1.JsonClone();
|
||||
var different = GivenEdition();
|
||||
|
||||
// make item2 different in the property under consideration
|
||||
if (prop.PropertyType == typeof(bool))
|
||||
{
|
||||
prop.SetValue(item2, !(bool)prop.GetValue(item1));
|
||||
}
|
||||
else
|
||||
{
|
||||
prop.SetValue(item2, prop.GetValue(different));
|
||||
}
|
||||
|
||||
item1.Should().NotBeSameAs(item2);
|
||||
item1.Should().NotBe(item2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void metadata_and_db_fields_should_replicate_edition()
|
||||
{
|
||||
var item1 = GivenEdition();
|
||||
var item2 = GivenEdition();
|
||||
|
||||
item1.Should().NotBe(item2);
|
||||
|
||||
item1.UseMetadataFrom(item2);
|
||||
item1.UseDbFieldsFrom(item2);
|
||||
item1.Should().Be(item2);
|
||||
}
|
||||
|
||||
private Author GivenArtist()
|
||||
{
|
||||
return _fixture.Build<Author>()
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Books;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MusicTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class RefreshAlbumServiceFixture : CoreTest<RefreshBookService>
|
||||
{
|
||||
private Author _artist;
|
||||
private List<Book> _albums;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var album1 = Builder<Book>.CreateNew()
|
||||
.With(x => x.AuthorMetadata = Builder<AuthorMetadata>.CreateNew().Build())
|
||||
.With(s => s.Id = 1234)
|
||||
.With(s => s.ForeignBookId = "1")
|
||||
.Build();
|
||||
|
||||
_albums = new List<Book> { album1 };
|
||||
|
||||
_artist = Builder<Author>.CreateNew()
|
||||
.With(s => s.Books = _albums)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IAuthorService>()
|
||||
.Setup(s => s.GetAuthor(_artist.Id))
|
||||
.Returns(_artist);
|
||||
|
||||
Mocker.GetMock<IAuthorMetadataService>()
|
||||
.Setup(s => s.UpsertMany(It.IsAny<List<AuthorMetadata>>()))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IProvideBookInfo>()
|
||||
.Setup(s => s.GetBookInfo(It.IsAny<string>()))
|
||||
.Callback(() => { throw new BookNotFoundException(album1.ForeignBookId); });
|
||||
|
||||
Mocker.GetMock<ICheckIfBookShouldBeRefreshed>()
|
||||
.Setup(s => s.ShouldRefresh(It.IsAny<Book>()))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(x => x.GetFilesByBook(It.IsAny<int>()))
|
||||
.Returns(new List<BookFile>());
|
||||
|
||||
Mocker.GetMock<IHistoryService>()
|
||||
.Setup(x => x.GetByBook(It.IsAny<int>(), It.IsAny<HistoryEventType?>()))
|
||||
.Returns(new List<History.History>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_update_if_musicbrainz_id_changed_and_no_clash()
|
||||
{
|
||||
var newAlbumInfo = _albums.First().JsonClone();
|
||||
newAlbumInfo.AuthorMetadata = _albums.First().AuthorMetadata.Value.JsonClone();
|
||||
newAlbumInfo.ForeignBookId = _albums.First().ForeignBookId + 1;
|
||||
|
||||
Subject.RefreshBookInfo(_albums, new List<Book> { newAlbumInfo }, null, false, false, null);
|
||||
|
||||
Mocker.GetMock<IBookService>()
|
||||
.Verify(v => v.UpdateMany(It.Is<List<Book>>(s => s.First().ForeignBookId == newAlbumInfo.ForeignBookId)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_merge_if_musicbrainz_id_changed_and_new_already_exists()
|
||||
{
|
||||
var existing = _albums.First();
|
||||
|
||||
var clash = existing.JsonClone();
|
||||
clash.Id = 100;
|
||||
clash.AuthorMetadata = existing.AuthorMetadata.Value.JsonClone();
|
||||
clash.ForeignBookId += 1;
|
||||
|
||||
Mocker.GetMock<IBookService>()
|
||||
.Setup(x => x.FindById(clash.ForeignBookId))
|
||||
.Returns(clash);
|
||||
|
||||
var newAlbumInfo = existing.JsonClone();
|
||||
newAlbumInfo.AuthorMetadata = existing.AuthorMetadata.Value.JsonClone();
|
||||
newAlbumInfo.ForeignBookId = _albums.First().ForeignBookId + 1;
|
||||
|
||||
Subject.RefreshBookInfo(_albums, new List<Book> { newAlbumInfo }, null, false, false, null);
|
||||
|
||||
// check old album is deleted
|
||||
Mocker.GetMock<IBookService>()
|
||||
.Verify(v => v.DeleteMany(It.Is<List<Book>>(x => x.First().ForeignBookId == existing.ForeignBookId)));
|
||||
|
||||
// check that clash gets updated
|
||||
Mocker.GetMock<IBookService>()
|
||||
.Verify(v => v.UpdateMany(It.Is<List<Book>>(s => s.First().ForeignBookId == newAlbumInfo.ForeignBookId)));
|
||||
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,10 +45,12 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||
|
||||
var metadata = Builder<AuthorMetadata>.CreateNew().Build();
|
||||
var series = Builder<Series>.CreateListOfSize(1).BuildList();
|
||||
var profile = Builder<MetadataProfile>.CreateNew().Build();
|
||||
|
||||
_artist = Builder<Author>.CreateNew()
|
||||
.With(a => a.Metadata = metadata)
|
||||
.With(a => a.Series = series)
|
||||
.With(a => a.MetadataProfile = profile)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IAuthorService>(MockBehavior.Strict)
|
||||
@@ -63,7 +65,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||
.Returns(_albums);
|
||||
|
||||
Mocker.GetMock<IProvideAuthorInfo>()
|
||||
.Setup(s => s.GetAuthorInfo(It.IsAny<string>()))
|
||||
.Setup(s => s.GetAuthorAndBooks(It.IsAny<string>(), It.IsAny<double>()))
|
||||
.Callback(() => { throw new AuthorNotFoundException(_artist.ForeignAuthorId); });
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
@@ -86,7 +88,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||
private void GivenNewArtistInfo(Author artist)
|
||||
{
|
||||
Mocker.GetMock<IProvideAuthorInfo>()
|
||||
.Setup(s => s.GetAuthorInfo(_artist.ForeignAuthorId))
|
||||
.Setup(s => s.GetAuthorAndBooks(_artist.ForeignAuthorId, It.IsAny<double>()))
|
||||
.Returns(artist);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,13 @@ namespace NzbDrone.Core.Test.OrganizerTests
|
||||
.With(s => s.Title = "Fake: Book")
|
||||
.Build();
|
||||
|
||||
Subject.BuildBookFilePath(fakeArtist, fakeAlbum, filename, ".mobi").Should().Be(expectedPath.AsOsAgnostic());
|
||||
var fakeEdition = Builder<Edition>
|
||||
.CreateNew()
|
||||
.With(s => s.Title = fakeAlbum.Title)
|
||||
.With(s => s.Book = fakeAlbum)
|
||||
.Build();
|
||||
|
||||
Subject.BuildBookFilePath(fakeArtist, fakeEdition, filename, ".mobi").Should().Be(expectedPath.AsOsAgnostic());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
private Author _artist;
|
||||
private Book _album;
|
||||
private Edition _edition;
|
||||
private BookFile _trackFile;
|
||||
private NamingConfig _namingConfig;
|
||||
|
||||
@@ -32,6 +33,12 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
.With(s => s.Title = "Hail to the King")
|
||||
.Build();
|
||||
|
||||
_edition = Builder<Edition>
|
||||
.CreateNew()
|
||||
.With(s => s.Title = _album.Title)
|
||||
.With(s => s.Book = _album)
|
||||
.Build();
|
||||
|
||||
_trackFile = new BookFile { Quality = new QualityModel(Quality.MP3_320), ReleaseGroup = "ReadarrTest" };
|
||||
|
||||
_namingConfig = NamingConfig.Default;
|
||||
@@ -68,7 +75,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_artist.Name = name;
|
||||
_namingConfig.StandardBookFormat = "{Author CleanName}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
private Author _artist;
|
||||
private Book _album;
|
||||
private Edition _edition;
|
||||
private BookFile _trackFile;
|
||||
private NamingConfig _namingConfig;
|
||||
|
||||
@@ -37,7 +38,13 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_album = Builder<Book>
|
||||
.CreateNew()
|
||||
.With(s => s.Title = "Hybrid Theory")
|
||||
.Build();
|
||||
|
||||
_edition = Builder<Edition>
|
||||
.CreateNew()
|
||||
.With(s => s.Title = _album.Title)
|
||||
.With(s => s.Disambiguation = "The Best Album")
|
||||
.With(s => s.Book = _album)
|
||||
.Build();
|
||||
|
||||
_namingConfig = NamingConfig.Default;
|
||||
@@ -78,7 +85,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author Name}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin Park");
|
||||
}
|
||||
|
||||
@@ -87,7 +94,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author_Name}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin_Park");
|
||||
}
|
||||
|
||||
@@ -96,7 +103,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author.Name}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin.Park");
|
||||
}
|
||||
|
||||
@@ -105,7 +112,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author-Name}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin-Park");
|
||||
}
|
||||
|
||||
@@ -114,7 +121,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{AUTHOR NAME}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("LINKIN PARK");
|
||||
}
|
||||
|
||||
@@ -123,7 +130,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{aUtHoR-nAmE}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(_artist.Name.Replace(' ', '-'));
|
||||
}
|
||||
|
||||
@@ -132,7 +139,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{author name}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("linkin park");
|
||||
}
|
||||
|
||||
@@ -142,7 +149,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_namingConfig.StandardBookFormat = "{Author.CleanName}";
|
||||
_artist.Name = "Linkin Park (1997)";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin.Park.1997");
|
||||
}
|
||||
|
||||
@@ -151,16 +158,16 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author Disambiguation}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("US Rock Band");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_replace_Album_space_Title()
|
||||
public void should_replace_edition_space_Title()
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Book Title}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Hybrid Theory");
|
||||
}
|
||||
|
||||
@@ -169,7 +176,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Book Disambiguation}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("The Best Album");
|
||||
}
|
||||
|
||||
@@ -178,7 +185,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Book_Title}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Hybrid_Theory");
|
||||
}
|
||||
|
||||
@@ -187,7 +194,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Book.Title}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Hybrid.Theory");
|
||||
}
|
||||
|
||||
@@ -196,7 +203,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Book-Title}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Hybrid-Theory");
|
||||
}
|
||||
|
||||
@@ -205,7 +212,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{BOOK TITLE}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("HYBRID THEORY");
|
||||
}
|
||||
|
||||
@@ -214,7 +221,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{bOoK-tItLE}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(_album.Title.Replace(' ', '-'));
|
||||
}
|
||||
|
||||
@@ -223,7 +230,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{book title}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("hybrid theory");
|
||||
}
|
||||
|
||||
@@ -233,7 +240,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_namingConfig.StandardBookFormat = "{Author.CleanName}";
|
||||
_artist.Name = "Hybrid Theory (2000)";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Hybrid.Theory.2000");
|
||||
}
|
||||
|
||||
@@ -242,7 +249,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Quality Title}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("MP3-320");
|
||||
}
|
||||
|
||||
@@ -251,7 +258,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{MediaInfo AudioCodec}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("FLAC");
|
||||
}
|
||||
|
||||
@@ -260,7 +267,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{MediaInfo AudioBitRate}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("320 kbps");
|
||||
}
|
||||
|
||||
@@ -269,7 +276,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{MediaInfo AudioChannels}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("2.0");
|
||||
}
|
||||
|
||||
@@ -278,7 +285,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{MediaInfo AudioBitsPerSample}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("16bit");
|
||||
}
|
||||
|
||||
@@ -287,7 +294,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{MediaInfo AudioSampleRate}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("44.1kHz");
|
||||
}
|
||||
|
||||
@@ -296,7 +303,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author Name} - {Book Title} - [{Quality Title}]";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin Park - Hybrid Theory - [MP3-320]");
|
||||
}
|
||||
|
||||
@@ -306,7 +313,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_namingConfig.RenameBooks = false;
|
||||
_trackFile.Path = "Linkin Park - 06 - Test";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.Path));
|
||||
}
|
||||
|
||||
@@ -317,7 +324,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_trackFile.Path = "Linkin Park - 06 - Test";
|
||||
_trackFile.SceneName = "SceneName";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.Path));
|
||||
}
|
||||
|
||||
@@ -327,7 +334,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_namingConfig.RenameBooks = false;
|
||||
_trackFile.Path = @"C:\Test\Unsorted\Artist - 01 - Test";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.Path));
|
||||
}
|
||||
|
||||
@@ -336,7 +343,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Release Group}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(_trackFile.ReleaseGroup);
|
||||
}
|
||||
|
||||
@@ -349,7 +356,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_trackFile.SceneName = "Linkin.Park.Meteora.320-LOL";
|
||||
_trackFile.Path = "30 Rock - 01 - Test";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin Park - Linkin.Park.Meteora.320-LOL");
|
||||
}
|
||||
|
||||
@@ -358,7 +365,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author.Name}.{Book.Title}";
|
||||
|
||||
Subject.BuildBookFileName(new Author { Name = "In The Woods." }, new Book { Title = "30 Rock" }, _trackFile)
|
||||
Subject.BuildBookFileName(new Author { Name = "In The Woods." }, new Edition { Title = "30 Rock" }, _trackFile)
|
||||
.Should().Be("In.The.Woods.30.Rock");
|
||||
}
|
||||
|
||||
@@ -367,7 +374,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author.Name}.{Book.Title}";
|
||||
|
||||
Subject.BuildBookFileName(new Author { Name = "In The Woods..." }, new Book { Title = "30 Rock" }, _trackFile)
|
||||
Subject.BuildBookFileName(new Author { Name = "In The Woods..." }, new Edition { Title = "30 Rock" }, _trackFile)
|
||||
.Should().Be("In.The.Woods.30.Rock");
|
||||
}
|
||||
|
||||
@@ -376,7 +383,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author.Name}{_Book.Title_}{Quality.Title}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin.Park_Hybrid.Theory_MP3-320");
|
||||
}
|
||||
|
||||
@@ -385,7 +392,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author.Name}{_Book.Title_}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin.Park_Hybrid.Theory");
|
||||
}
|
||||
|
||||
@@ -395,7 +402,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_artist.Name = "Venture Bros.";
|
||||
_namingConfig.StandardBookFormat = "{Author.Name}.{Book.Title}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Venture.Bros.Hybrid.Theory");
|
||||
}
|
||||
|
||||
@@ -408,7 +415,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_trackFile.SceneName = null;
|
||||
_trackFile.Path = "existing.file.mkv";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(Path.GetFileNameWithoutExtension(_trackFile.Path));
|
||||
}
|
||||
|
||||
@@ -421,7 +428,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_trackFile.SceneName = "30.Rock.S01E01.xvid-LOL";
|
||||
_trackFile.Path = "30 Rock - S01E01 - Test";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("30.Rock.S01E01.xvid-LOL");
|
||||
}
|
||||
|
||||
@@ -430,7 +437,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Quality Title} {Quality Proper}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("MP3-320");
|
||||
}
|
||||
|
||||
@@ -439,7 +446,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author Name} - {Book Title} [{Quality Title}] {[Quality Proper]}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin Park - Hybrid Theory [MP3-320]");
|
||||
}
|
||||
|
||||
@@ -448,7 +455,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = "{Author Name} - {Book Title} [{Quality Full}]";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Linkin Park - Hybrid Theory [MP3-320]");
|
||||
}
|
||||
|
||||
@@ -460,7 +467,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = string.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}", separator);
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("MP3-320");
|
||||
}
|
||||
|
||||
@@ -472,7 +479,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
_namingConfig.StandardBookFormat = string.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}{0}{{Book{0}Title}}", separator);
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(string.Format("MP3-320{0}Hybrid{0}Theory", separator));
|
||||
}
|
||||
|
||||
@@ -485,7 +492,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_trackFile.SceneName = "30.Rock.S01E01.xvid-LOL";
|
||||
_trackFile.Path = "30 Rock - S01E01 - Test";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("30 Rock - 30 Rock - S01E01 - Test");
|
||||
}
|
||||
|
||||
@@ -498,7 +505,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_trackFile.SceneName = "30.Rock.S01E01.xvid-LOL";
|
||||
_trackFile.Path = "30 Rock - S01E01 - Test";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("30 Rock - S01E01 - Test");
|
||||
}
|
||||
|
||||
@@ -508,7 +515,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_trackFile.ReleaseGroup = null;
|
||||
_namingConfig.StandardBookFormat = "{Release Group}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be("Readarr");
|
||||
}
|
||||
|
||||
@@ -520,7 +527,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_trackFile.ReleaseGroup = null;
|
||||
_namingConfig.StandardBookFormat = pattern;
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(expectedFileName);
|
||||
}
|
||||
|
||||
@@ -532,7 +539,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_trackFile.ReleaseGroup = releaseGroup;
|
||||
_namingConfig.StandardBookFormat = "{Release Group}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(releaseGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
private Author _artist;
|
||||
private Book _album;
|
||||
private Edition _edition;
|
||||
private BookFile _trackFile;
|
||||
private NamingConfig _namingConfig;
|
||||
|
||||
@@ -32,6 +33,12 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
.With(s => s.Title = "Anthology")
|
||||
.Build();
|
||||
|
||||
_edition = Builder<Edition>
|
||||
.CreateNew()
|
||||
.With(s => s.Title = _album.Title)
|
||||
.With(s => s.Book = _album)
|
||||
.Build();
|
||||
|
||||
_trackFile = new BookFile { Quality = new QualityModel(Quality.MP3_320), ReleaseGroup = "ReadarrTest" };
|
||||
|
||||
_namingConfig = NamingConfig.Default;
|
||||
@@ -62,7 +69,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_artist.Name = name;
|
||||
_namingConfig.StandardBookFormat = "{Author NameThe}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(expected);
|
||||
}
|
||||
|
||||
@@ -75,7 +82,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
_artist.Name = name;
|
||||
_namingConfig.StandardBookFormat = "{Author NameThe}";
|
||||
|
||||
Subject.BuildBookFileName(_artist, _album, _trackFile)
|
||||
Subject.BuildBookFileName(_artist, _edition, _trackFile)
|
||||
.Should().Be(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.AuthorStats
|
||||
|
||||
public class AuthorStatisticsRepository : IAuthorStatisticsRepository
|
||||
{
|
||||
private const string _selectTemplate = "SELECT /**select**/ FROM Books /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
||||
private const string _selectTemplate = "SELECT /**select**/ FROM Editions /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
||||
|
||||
private readonly IMainDatabase _database;
|
||||
|
||||
@@ -28,14 +28,22 @@ namespace NzbDrone.Core.AuthorStats
|
||||
public List<BookStatistics> AuthorStatistics()
|
||||
{
|
||||
var time = DateTime.UtcNow;
|
||||
return Query(Builder().Where<Book>(x => x.ReleaseDate < time));
|
||||
var stats = Query(Builder());
|
||||
|
||||
#pragma warning disable CS0472
|
||||
return Query(Builder().OrWhere<Book>(x => x.ReleaseDate < time)
|
||||
.OrWhere<BookFile>(x => x.Id != null));
|
||||
#pragma warning restore
|
||||
}
|
||||
|
||||
public List<BookStatistics> AuthorStatistics(int authorId)
|
||||
{
|
||||
var time = DateTime.UtcNow;
|
||||
return Query(Builder().Where<Book>(x => x.ReleaseDate < time)
|
||||
#pragma warning disable CS0472
|
||||
return Query(Builder().OrWhere<Book>(x => x.ReleaseDate < time)
|
||||
.OrWhere<BookFile>(x => x.Id != null)
|
||||
.Where<Author>(x => x.Id == authorId));
|
||||
#pragma warning restore
|
||||
}
|
||||
|
||||
private List<BookStatistics> Query(SqlBuilder builder)
|
||||
@@ -56,8 +64,10 @@ namespace NzbDrone.Core.AuthorStats
|
||||
SUM(CASE WHEN BookFiles.Id IS NULL THEN 0 ELSE 1 END) AS AvailableBookCount,
|
||||
SUM(CASE WHEN Books.Monitored = 1 OR BookFiles.Id IS NOT NULL THEN 1 ELSE 0 END) AS BookCount,
|
||||
SUM(CASE WHEN BookFiles.Id IS NULL THEN 0 ELSE 1 END) AS BookFileCount")
|
||||
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
||||
.LeftJoin<Book, BookFile>((t, f) => t.Id == f.BookId)
|
||||
.LeftJoin<Edition, BookFile>((t, f) => t.Id == f.EditionId)
|
||||
.Where<Edition>(x => x.Monitored == true)
|
||||
.GroupBy<Author>(x => x.Id)
|
||||
.GroupBy<Book>(x => x.Id);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace NzbDrone.Core.Books.Calibre
|
||||
|
||||
public void SetFields(BookFile file, CalibreSettings settings)
|
||||
{
|
||||
var book = file.Book.Value;
|
||||
var book = file.Edition.Value;
|
||||
|
||||
var cover = book.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Cover);
|
||||
string image = null;
|
||||
@@ -144,7 +144,6 @@ namespace NzbDrone.Core.Books.Calibre
|
||||
rating = book.Ratings.Value * 2,
|
||||
identifiers = new Dictionary<string, string>
|
||||
{
|
||||
{ "goodreads", book.GoodreadsId.ToString() },
|
||||
{ "isbn", book.Isbn13 },
|
||||
{ "asin", book.Asin }
|
||||
}
|
||||
|
||||
14
src/NzbDrone.Core/Books/Events/EditionDeletedEvent.cs
Normal file
14
src/NzbDrone.Core/Books/Events/EditionDeletedEvent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
|
||||
namespace NzbDrone.Core.Books.Events
|
||||
{
|
||||
public class EditionDeletedEvent : IEvent
|
||||
{
|
||||
public Edition Edition { get; private set; }
|
||||
|
||||
public EditionDeletedEvent(Edition edition)
|
||||
{
|
||||
Edition = edition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -16,13 +17,15 @@ namespace NzbDrone.Core.Books
|
||||
}
|
||||
|
||||
public string ForeignAuthorId { get; set; }
|
||||
public int GoodreadsId { get; set; }
|
||||
public string TitleSlug { get; set; }
|
||||
public string Name { get; set; }
|
||||
public List<string> Aliases { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public string Disambiguation { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Gender { get; set; }
|
||||
public string Hometown { get; set; }
|
||||
public DateTime? Born { get; set; }
|
||||
public DateTime? Died { get; set; }
|
||||
public AuthorStatusType Status { get; set; }
|
||||
public List<MediaCover.MediaCover> Images { get; set; }
|
||||
public List<Links> Links { get; set; }
|
||||
@@ -37,13 +40,15 @@ namespace NzbDrone.Core.Books
|
||||
public override void UseMetadataFrom(AuthorMetadata other)
|
||||
{
|
||||
ForeignAuthorId = other.ForeignAuthorId;
|
||||
GoodreadsId = other.GoodreadsId;
|
||||
TitleSlug = other.TitleSlug;
|
||||
Name = other.Name;
|
||||
Aliases = other.Aliases;
|
||||
Overview = other.Overview.IsNullOrWhiteSpace() ? Overview : other.Overview;
|
||||
Disambiguation = other.Disambiguation;
|
||||
Type = other.Type;
|
||||
Gender = other.Gender;
|
||||
Hometown = other.Hometown;
|
||||
Born = other.Born;
|
||||
Died = other.Died;
|
||||
Status = other.Status;
|
||||
Images = other.Images.Any() ? other.Images : Images;
|
||||
Links = other.Links;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Equ;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -13,8 +12,6 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
public Book()
|
||||
{
|
||||
Overview = string.Empty;
|
||||
Images = new List<MediaCover.MediaCover>();
|
||||
Links = new List<Links>();
|
||||
Genres = new List<string>();
|
||||
Ratings = new Ratings();
|
||||
@@ -26,19 +23,9 @@ namespace NzbDrone.Core.Books
|
||||
// These are metadata entries
|
||||
public int AuthorMetadataId { get; set; }
|
||||
public string ForeignBookId { get; set; }
|
||||
public string ForeignWorkId { get; set; }
|
||||
public int GoodreadsId { get; set; }
|
||||
public string TitleSlug { get; set; }
|
||||
public string Isbn13 { get; set; }
|
||||
public string Asin { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Language { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public string Disambiguation { get; set; }
|
||||
public string Publisher { get; set; }
|
||||
public int PageCount { get; set; }
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
public List<MediaCover.MediaCover> Images { get; set; }
|
||||
public List<Links> Links { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public Ratings Ratings { get; set; }
|
||||
@@ -46,6 +33,7 @@ namespace NzbDrone.Core.Books
|
||||
// These are Readarr generated/config
|
||||
public string CleanTitle { get; set; }
|
||||
public bool Monitored { get; set; }
|
||||
public bool AnyEditionOk { get; set; }
|
||||
public DateTime? LastInfoSync { get; set; }
|
||||
public DateTime Added { get; set; }
|
||||
[MemberwiseEqualityIgnore]
|
||||
@@ -57,6 +45,8 @@ namespace NzbDrone.Core.Books
|
||||
[MemberwiseEqualityIgnore]
|
||||
public LazyLoaded<Author> Author { get; set; }
|
||||
[MemberwiseEqualityIgnore]
|
||||
public LazyLoaded<List<Edition>> Editions { get; set; }
|
||||
[MemberwiseEqualityIgnore]
|
||||
public LazyLoaded<List<BookFile>> BookFiles { get; set; }
|
||||
[MemberwiseEqualityIgnore]
|
||||
public LazyLoaded<List<SeriesBookLink>> SeriesLinks { get; set; }
|
||||
@@ -77,19 +67,9 @@ namespace NzbDrone.Core.Books
|
||||
public override void UseMetadataFrom(Book other)
|
||||
{
|
||||
ForeignBookId = other.ForeignBookId;
|
||||
ForeignWorkId = other.ForeignWorkId;
|
||||
GoodreadsId = other.GoodreadsId;
|
||||
TitleSlug = other.TitleSlug;
|
||||
Isbn13 = other.Isbn13;
|
||||
Asin = other.Asin;
|
||||
Title = other.Title;
|
||||
Language = other.Language;
|
||||
Overview = other.Overview.IsNullOrWhiteSpace() ? Overview : other.Overview;
|
||||
Disambiguation = other.Disambiguation;
|
||||
Publisher = other.Publisher;
|
||||
PageCount = other.PageCount;
|
||||
ReleaseDate = other.ReleaseDate;
|
||||
Images = other.Images.Any() ? other.Images : Images;
|
||||
Links = other.Links;
|
||||
Genres = other.Genres;
|
||||
Ratings = other.Ratings;
|
||||
@@ -101,6 +81,7 @@ namespace NzbDrone.Core.Books
|
||||
Id = other.Id;
|
||||
AuthorMetadataId = other.AuthorMetadataId;
|
||||
Monitored = other.Monitored;
|
||||
AnyEditionOk = other.AnyEditionOk;
|
||||
LastInfoSync = other.LastInfoSync;
|
||||
Added = other.Added;
|
||||
AddOptions = other.AddOptions;
|
||||
@@ -109,9 +90,9 @@ namespace NzbDrone.Core.Books
|
||||
public override void ApplyChanges(Book other)
|
||||
{
|
||||
ForeignBookId = other.ForeignBookId;
|
||||
ForeignWorkId = other.ForeignWorkId;
|
||||
AddOptions = other.AddOptions;
|
||||
Monitored = other.Monitored;
|
||||
AnyEditionOk = other.AnyEditionOk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
91
src/NzbDrone.Core/Books/Model/Edition.cs
Normal file
91
src/NzbDrone.Core/Books/Model/Edition.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Equ;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.Books
|
||||
{
|
||||
public class Edition : Entity<Edition>
|
||||
{
|
||||
public Edition()
|
||||
{
|
||||
Overview = string.Empty;
|
||||
Images = new List<MediaCover.MediaCover>();
|
||||
Links = new List<Links>();
|
||||
Ratings = new Ratings();
|
||||
}
|
||||
|
||||
// These correspond to columns in the Albums table
|
||||
// These are metadata entries
|
||||
public int BookId { get; set; }
|
||||
public string ForeignEditionId { get; set; }
|
||||
public string TitleSlug { get; set; }
|
||||
public string Isbn13 { get; set; }
|
||||
public string Asin { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Language { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public string Format { get; set; }
|
||||
public bool IsEbook { get; set; }
|
||||
public string Disambiguation { get; set; }
|
||||
public string Publisher { get; set; }
|
||||
public int PageCount { get; set; }
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
public List<MediaCover.MediaCover> Images { get; set; }
|
||||
public List<Links> Links { get; set; }
|
||||
public Ratings Ratings { get; set; }
|
||||
|
||||
// These are Readarr generated/config
|
||||
public bool Monitored { get; set; }
|
||||
public bool ManualAdd { get; set; }
|
||||
|
||||
// These are dynamically queried from other tables
|
||||
[MemberwiseEqualityIgnore]
|
||||
public LazyLoaded<Book> Book { get; set; }
|
||||
[MemberwiseEqualityIgnore]
|
||||
public LazyLoaded<List<BookFile>> BookFiles { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0}][{1}]", ForeignEditionId, Title.NullSafe());
|
||||
}
|
||||
|
||||
public override void UseMetadataFrom(Edition other)
|
||||
{
|
||||
ForeignEditionId = other.ForeignEditionId;
|
||||
TitleSlug = other.TitleSlug;
|
||||
Isbn13 = other.Isbn13;
|
||||
Asin = other.Asin;
|
||||
Title = other.Title;
|
||||
Language = other.Language;
|
||||
Overview = other.Overview.IsNullOrWhiteSpace() ? Overview : other.Overview;
|
||||
Format = other.Format;
|
||||
IsEbook = other.IsEbook;
|
||||
Disambiguation = other.Disambiguation;
|
||||
Publisher = other.Publisher;
|
||||
PageCount = other.PageCount;
|
||||
ReleaseDate = other.ReleaseDate;
|
||||
Images = other.Images.Any() ? other.Images : Images;
|
||||
Links = other.Links;
|
||||
Ratings = other.Ratings;
|
||||
}
|
||||
|
||||
public override void UseDbFieldsFrom(Edition other)
|
||||
{
|
||||
Id = other.Id;
|
||||
BookId = other.BookId;
|
||||
Book = other.Book;
|
||||
Monitored = other.Monitored;
|
||||
ManualAdd = other.ManualAdd;
|
||||
}
|
||||
|
||||
public override void ApplyChanges(Edition other)
|
||||
{
|
||||
ForeignEditionId = other.ForeignEditionId;
|
||||
Monitored = other.Monitored;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,7 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
public int Votes { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
|
||||
public double Popularity => (double)Value * Votes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace NzbDrone.Core.Books
|
||||
public List<Book> GetBooksByFileIds(IEnumerable<int> fileIds)
|
||||
{
|
||||
return Query(new SqlBuilder()
|
||||
.Join<Book, BookFile>((l, r) => l.Id == r.BookId)
|
||||
.Join<Book, BookFile>((l, r) => l.Id == r.EditionId)
|
||||
.Where<BookFile>(f => fileIds.Contains(f.Id)))
|
||||
.DistinctBy(x => x.Id)
|
||||
.ToList();
|
||||
@@ -90,7 +90,7 @@ namespace NzbDrone.Core.Books
|
||||
#pragma warning disable CS0472
|
||||
private SqlBuilder AlbumsWithoutFilesBuilder(DateTime currentTime) => Builder()
|
||||
.Join<Book, Author>((l, r) => l.AuthorMetadataId == r.AuthorMetadataId)
|
||||
.LeftJoin<Book, BookFile>((t, f) => t.Id == f.BookId)
|
||||
.LeftJoin<Book, BookFile>((t, f) => t.Id == f.EditionId)
|
||||
.Where<BookFile>(f => f.Id == null)
|
||||
.Where<Book>(a => a.ReleaseDate <= currentTime);
|
||||
#pragma warning restore CS0472
|
||||
@@ -107,7 +107,7 @@ namespace NzbDrone.Core.Books
|
||||
|
||||
private SqlBuilder AlbumsWhereCutoffUnmetBuilder(List<QualitiesBelowCutoff> qualitiesBelowCutoff) => Builder()
|
||||
.Join<Book, Author>((l, r) => l.AuthorMetadataId == r.AuthorMetadataId)
|
||||
.Join<Book, BookFile>((t, f) => t.Id == f.BookId)
|
||||
.Join<Book, BookFile>((t, f) => t.Id == f.EditionId)
|
||||
.Where(BuildQualityCutoffWhereClause(qualitiesBelowCutoff));
|
||||
|
||||
private string BuildQualityCutoffWhereClause(List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
||||
@@ -193,7 +193,7 @@ namespace NzbDrone.Core.Books
|
||||
public List<Book> GetAuthorBooksWithFiles(Author author)
|
||||
{
|
||||
return Query(Builder()
|
||||
.Join<Book, BookFile>((t, f) => t.Id == f.BookId)
|
||||
.Join<Book, BookFile>((t, f) => t.Id == f.EditionId)
|
||||
.Where<Book>(x => x.AuthorMetadataId == author.AuthorMetadataId));
|
||||
}
|
||||
}
|
||||
|
||||
75
src/NzbDrone.Core/Books/Repositories/EditionRepository.cs
Normal file
75
src/NzbDrone.Core/Books/Repositories/EditionRepository.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Books
|
||||
{
|
||||
public interface IEditionRepository : IBasicRepository<Edition>
|
||||
{
|
||||
Edition FindByForeignEditionId(string foreignEditionId);
|
||||
List<Edition> FindByBook(int id);
|
||||
List<Edition> FindByAuthor(int id);
|
||||
List<Edition> GetEditionsForRefresh(int albumId, IEnumerable<string> foreignEditionIds);
|
||||
List<Edition> SetMonitored(Edition edition);
|
||||
}
|
||||
|
||||
public class EditionRepository : BasicRepository<Edition>, IEditionRepository
|
||||
{
|
||||
public EditionRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
}
|
||||
|
||||
public Edition FindByForeignEditionId(string foreignEditionId)
|
||||
{
|
||||
var edition = Query(x => x.ForeignEditionId == foreignEditionId).SingleOrDefault();
|
||||
|
||||
return edition;
|
||||
}
|
||||
|
||||
public List<Edition> GetEditionsForRefresh(int albumId, IEnumerable<string> foreignEditionIds)
|
||||
{
|
||||
return Query(r => r.BookId == albumId || foreignEditionIds.Contains(r.ForeignEditionId));
|
||||
}
|
||||
|
||||
public List<Edition> FindByBook(int id)
|
||||
{
|
||||
// populate the albums and artist metadata also
|
||||
// this hopefully speeds up the track matching a lot
|
||||
var builder = new SqlBuilder()
|
||||
.LeftJoin<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||
.LeftJoin<Book, AuthorMetadata>((b, a) => b.AuthorMetadataId == a.Id)
|
||||
.Where<Edition>(r => r.BookId == id);
|
||||
|
||||
return _database.QueryJoined<Edition, Book, AuthorMetadata>(builder, (edition, book, metadata) =>
|
||||
{
|
||||
if (book != null)
|
||||
{
|
||||
book.AuthorMetadata = metadata;
|
||||
edition.Book = book;
|
||||
}
|
||||
|
||||
return edition;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public List<Edition> FindByAuthor(int id)
|
||||
{
|
||||
return Query(Builder().Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||
.Join<Book, Author>((b, a) => b.AuthorMetadataId == a.AuthorMetadataId)
|
||||
.Where<Author>(a => a.Id == id));
|
||||
}
|
||||
|
||||
public List<Edition> SetMonitored(Edition edition)
|
||||
{
|
||||
var allEditions = FindByBook(edition.BookId);
|
||||
allEditions.ForEach(r => r.Monitored = r.Id == edition.Id);
|
||||
Ensure.That(allEditions.Count(x => x.Monitored) == 1).IsTrue();
|
||||
UpdateMany(allEditions);
|
||||
return allEditions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Books
|
||||
List<Author> AddAuthors(List<Author> newAuthors, bool doRefresh = true);
|
||||
}
|
||||
|
||||
public class AddArtistService : IAddAuthorService
|
||||
public class AddAuthorService : IAddAuthorService
|
||||
{
|
||||
private readonly IAuthorService _authorService;
|
||||
private readonly IAuthorMetadataService _authorMetadataService;
|
||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.Books
|
||||
private readonly IAddAuthorValidator _addAuthorValidator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AddArtistService(IAuthorService authorService,
|
||||
public AddAuthorService(IAuthorService authorService,
|
||||
IAuthorMetadataService authorMetadataService,
|
||||
IProvideAuthorInfo authorInfo,
|
||||
IBuildFileNames fileNameBuilder,
|
||||
|
||||
@@ -44,7 +44,17 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
_logger.Debug($"Adding book {book}");
|
||||
|
||||
// we allow adding extra editions, so check if the book already exists
|
||||
var dbBook = _bookService.FindById(book.ForeignBookId);
|
||||
if (dbBook != null)
|
||||
{
|
||||
dbBook.Editions = book.Editions;
|
||||
book = dbBook;
|
||||
}
|
||||
else
|
||||
{
|
||||
book = AddSkyhookData(book);
|
||||
}
|
||||
|
||||
// Remove any import list exclusions preventing addition
|
||||
_importListExclusionService.Delete(book.ForeignBookId);
|
||||
@@ -98,7 +108,7 @@ namespace NzbDrone.Core.Books
|
||||
Tuple<string, Book, List<AuthorMetadata>> tuple = null;
|
||||
try
|
||||
{
|
||||
tuple = _bookInfo.GetBookInfo(newBook.ForeignBookId);
|
||||
tuple = _bookInfo.GetBookInfo(newBook.Editions.Value.Single(x => x.Monitored).ForeignEditionId);
|
||||
}
|
||||
catch (BookNotFoundException)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@@ -45,21 +46,32 @@ namespace NzbDrone.Core.Books
|
||||
IHandle<AuthorDeletedEvent>
|
||||
{
|
||||
private readonly IBookRepository _bookRepository;
|
||||
private readonly IEditionService _editionService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public BookService(IBookRepository bookRepository,
|
||||
IEditionService editionService,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
{
|
||||
_bookRepository = bookRepository;
|
||||
_editionService = editionService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Book AddBook(Book newBook, bool doRefresh = true)
|
||||
{
|
||||
_bookRepository.Insert(newBook);
|
||||
var editions = newBook.Editions.Value;
|
||||
editions.ForEach(x => x.Monitored = newBook.Id > 0);
|
||||
|
||||
_bookRepository.Upsert(newBook);
|
||||
|
||||
editions.ForEach(x => x.BookId = newBook.Id);
|
||||
|
||||
_editionService.InsertMany(editions);
|
||||
_editionService.SetMonitored(editions.First());
|
||||
|
||||
_eventAggregator.PublishEvent(new BookAddedEvent(GetBook(newBook.Id), doRefresh));
|
||||
|
||||
|
||||
95
src/NzbDrone.Core/Books/Services/EditionService.cs
Normal file
95
src/NzbDrone.Core/Books/Services/EditionService.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Books.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Books
|
||||
{
|
||||
public interface IEditionService
|
||||
{
|
||||
Edition GetEdition(int id);
|
||||
Edition GetEditionByForeignEditionId(string foreignEditionId);
|
||||
List<Edition> GetAllEditions();
|
||||
void InsertMany(List<Edition> editions);
|
||||
void UpdateMany(List<Edition> editions);
|
||||
void DeleteMany(List<Edition> editions);
|
||||
List<Edition> GetEditionsForRefresh(int albumId, IEnumerable<string> foreignEditionIds);
|
||||
List<Edition> GetEditionsByBook(int bookId);
|
||||
List<Edition> GetEditionsByAuthor(int authorId);
|
||||
List<Edition> SetMonitored(Edition edition);
|
||||
}
|
||||
|
||||
public class EditionService : IEditionService,
|
||||
IHandle<BookDeletedEvent>
|
||||
{
|
||||
private readonly IEditionRepository _editionRepository;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
public EditionService(IEditionRepository editionRepository,
|
||||
IEventAggregator eventAggregator)
|
||||
{
|
||||
_editionRepository = editionRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
public Edition GetEdition(int id)
|
||||
{
|
||||
return _editionRepository.Get(id);
|
||||
}
|
||||
|
||||
public Edition GetEditionByForeignEditionId(string foreignEditionId)
|
||||
{
|
||||
return _editionRepository.FindByForeignEditionId(foreignEditionId);
|
||||
}
|
||||
|
||||
public List<Edition> GetAllEditions()
|
||||
{
|
||||
return _editionRepository.All().ToList();
|
||||
}
|
||||
|
||||
public void InsertMany(List<Edition> editions)
|
||||
{
|
||||
_editionRepository.InsertMany(editions);
|
||||
}
|
||||
|
||||
public void UpdateMany(List<Edition> editions)
|
||||
{
|
||||
_editionRepository.UpdateMany(editions);
|
||||
}
|
||||
|
||||
public void DeleteMany(List<Edition> editions)
|
||||
{
|
||||
_editionRepository.DeleteMany(editions);
|
||||
foreach (var edition in editions)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new EditionDeletedEvent(edition));
|
||||
}
|
||||
}
|
||||
|
||||
public List<Edition> GetEditionsForRefresh(int albumId, IEnumerable<string> foreignEditionIds)
|
||||
{
|
||||
return _editionRepository.GetEditionsForRefresh(albumId, foreignEditionIds);
|
||||
}
|
||||
|
||||
public List<Edition> GetEditionsByBook(int bookId)
|
||||
{
|
||||
return _editionRepository.FindByBook(bookId);
|
||||
}
|
||||
|
||||
public List<Edition> GetEditionsByAuthor(int authorId)
|
||||
{
|
||||
return _editionRepository.FindByAuthor(authorId);
|
||||
}
|
||||
|
||||
public List<Edition> SetMonitored(Edition edition)
|
||||
{
|
||||
return _editionRepository.SetMonitored(edition);
|
||||
}
|
||||
|
||||
public void Handle(BookDeletedEvent message)
|
||||
{
|
||||
var editions = GetEditionsByBook(message.Book.Id);
|
||||
DeleteMany(editions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,12 @@ using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.Books
|
||||
{
|
||||
public interface IRefreshAuthorService
|
||||
{
|
||||
}
|
||||
|
||||
public class RefreshAuthorService : RefreshEntityServiceBase<Author, Book>,
|
||||
IRefreshAuthorService,
|
||||
IExecute<RefreshAuthorCommand>,
|
||||
IExecute<BulkRefreshAuthorCommand>
|
||||
{
|
||||
@@ -76,11 +81,11 @@ namespace NzbDrone.Core.Books
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private Author GetSkyhookData(string foreignId)
|
||||
private Author GetSkyhookData(string foreignId, double minPopularity)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _authorInfo.GetAuthorInfo(foreignId);
|
||||
return _authorInfo.GetAuthorAndBooks(foreignId, minPopularity);
|
||||
}
|
||||
catch (AuthorNotFoundException)
|
||||
{
|
||||
@@ -278,7 +283,6 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
// little hack - trigger the series update here
|
||||
_refreshSeriesService.RefreshSeriesInfo(entity.AuthorMetadataId, entity.Series, entity, false, false, null);
|
||||
|
||||
_eventAggregator.PublishEvent(new AuthorRefreshCompleteEvent(entity));
|
||||
}
|
||||
|
||||
@@ -332,7 +336,7 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = GetSkyhookData(author.ForeignAuthorId);
|
||||
var data = GetSkyhookData(author.ForeignAuthorId, author.MetadataProfile.Value.MinPopularity);
|
||||
updated |= RefreshEntityInfo(author, null, data, true, false, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -381,7 +385,7 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = GetSkyhookData(author.ForeignAuthorId);
|
||||
var data = GetSkyhookData(author.ForeignAuthorId, author.MetadataProfile.Value.MinPopularity);
|
||||
updated |= RefreshEntityInfo(author, null, data, manualTrigger, false, message.LastStartTime);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Books.Events;
|
||||
using NzbDrone.Core.History;
|
||||
@@ -18,12 +20,14 @@ namespace NzbDrone.Core.Books
|
||||
bool RefreshBookInfo(List<Book> books, List<Book> remoteBooks, Author remoteData, bool forceBookRefresh, bool forceUpdateFileTags, DateTime? lastUpdate);
|
||||
}
|
||||
|
||||
public class RefreshBookService : RefreshEntityServiceBase<Book, object>, IRefreshBookService
|
||||
public class RefreshBookService : RefreshEntityServiceBase<Book, Edition>, IRefreshBookService
|
||||
{
|
||||
private readonly IBookService _bookService;
|
||||
private readonly IAuthorService _authorService;
|
||||
private readonly IAddAuthorService _addAuthorService;
|
||||
private readonly IEditionService _editionService;
|
||||
private readonly IProvideBookInfo _bookInfo;
|
||||
private readonly IRefreshEditionService _refreshEditionService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
@@ -34,8 +38,10 @@ namespace NzbDrone.Core.Books
|
||||
public RefreshBookService(IBookService bookService,
|
||||
IAuthorService authorService,
|
||||
IAddAuthorService addAuthorService,
|
||||
IEditionService editionService,
|
||||
IAuthorMetadataService authorMetadataService,
|
||||
IProvideBookInfo bookInfo,
|
||||
IRefreshEditionService refreshEditionService,
|
||||
IMediaFileService mediaFileService,
|
||||
IHistoryService historyService,
|
||||
IEventAggregator eventAggregator,
|
||||
@@ -47,7 +53,9 @@ namespace NzbDrone.Core.Books
|
||||
_bookService = bookService;
|
||||
_authorService = authorService;
|
||||
_addAuthorService = addAuthorService;
|
||||
_editionService = editionService;
|
||||
_bookInfo = bookInfo;
|
||||
_refreshEditionService = refreshEditionService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_historyService = historyService;
|
||||
_eventAggregator = eventAggregator;
|
||||
@@ -60,7 +68,7 @@ namespace NzbDrone.Core.Books
|
||||
{
|
||||
var result = new RemoteData();
|
||||
|
||||
var book = remote.SingleOrDefault(x => x.ForeignWorkId == local.ForeignWorkId);
|
||||
var book = remote.SingleOrDefault(x => x.ForeignBookId == local.ForeignBookId);
|
||||
|
||||
if (book == null && ShouldDelete(local))
|
||||
{
|
||||
@@ -69,7 +77,7 @@ namespace NzbDrone.Core.Books
|
||||
|
||||
if (book == null)
|
||||
{
|
||||
book = data.Books.Value.SingleOrDefault(x => x.ForeignWorkId == local.ForeignWorkId);
|
||||
book = data.Books.Value.SingleOrDefault(x => x.ForeignBookId == local.ForeignBookId);
|
||||
}
|
||||
|
||||
result.Entity = book;
|
||||
@@ -167,7 +175,7 @@ namespace NzbDrone.Core.Books
|
||||
|
||||
// Update book ids for trackfiles
|
||||
var files = _mediaFileService.GetFilesByBook(local.Id);
|
||||
files.ForEach(x => x.BookId = target.Id);
|
||||
files.ForEach(x => x.EditionId = target.Id);
|
||||
_mediaFileService.Update(files);
|
||||
|
||||
// Update book ids for history
|
||||
@@ -197,36 +205,70 @@ namespace NzbDrone.Core.Books
|
||||
_bookService.DeleteBook(local.Id, true);
|
||||
}
|
||||
|
||||
protected override List<object> GetRemoteChildren(Book local, Book remote)
|
||||
protected override List<Edition> GetRemoteChildren(Book local, Book remote)
|
||||
{
|
||||
return new List<object>();
|
||||
return remote.Editions.Value.DistinctBy(m => m.ForeignEditionId).ToList();
|
||||
}
|
||||
|
||||
protected override List<object> GetLocalChildren(Book entity, List<object> remoteChildren)
|
||||
protected override List<Edition> GetLocalChildren(Book entity, List<Edition> remoteChildren)
|
||||
{
|
||||
return new List<object>();
|
||||
return _editionService.GetEditionsForRefresh(entity.Id, remoteChildren.Select(x => x.ForeignEditionId));
|
||||
}
|
||||
|
||||
protected override Tuple<object, List<object>> GetMatchingExistingChildren(List<object> existingChildren, object remote)
|
||||
protected override Tuple<Edition, List<Edition>> GetMatchingExistingChildren(List<Edition> existingChildren, Edition remote)
|
||||
{
|
||||
return null;
|
||||
var existingChild = existingChildren.SingleOrDefault(x => x.ForeignEditionId == remote.ForeignEditionId);
|
||||
return Tuple.Create(existingChild, new List<Edition>());
|
||||
}
|
||||
|
||||
protected override void PrepareNewChild(object child, Book entity)
|
||||
protected override void PrepareNewChild(Edition child, Book entity)
|
||||
{
|
||||
child.BookId = entity.Id;
|
||||
child.Book = entity;
|
||||
}
|
||||
|
||||
protected override void PrepareExistingChild(object local, object remote, Book entity)
|
||||
protected override void PrepareExistingChild(Edition local, Edition remote, Book entity)
|
||||
{
|
||||
local.BookId = entity.Id;
|
||||
local.Book = entity;
|
||||
|
||||
remote.UseDbFieldsFrom(local);
|
||||
}
|
||||
|
||||
protected override void AddChildren(List<object> children)
|
||||
protected override void AddChildren(List<Edition> children)
|
||||
{
|
||||
// hack - add the chilren in refresh children so we can control monitored status
|
||||
}
|
||||
|
||||
protected override bool RefreshChildren(SortedChildren localChildren, List<object> remoteChildren, Author remoteData, bool forceChildRefresh, bool forceUpdateFileTags, DateTime? lastUpdate)
|
||||
private void MonitorSingleEdition(List<Edition> releases)
|
||||
{
|
||||
return false;
|
||||
var monitored = releases.Where(x => x.Monitored).ToList();
|
||||
if (!monitored.Any())
|
||||
{
|
||||
monitored = releases;
|
||||
}
|
||||
|
||||
var toMonitor = monitored.OrderByDescending(x => _mediaFileService.GetFilesByEdition(x.Id).Count)
|
||||
.ThenByDescending(x => x.Ratings.Votes)
|
||||
.First();
|
||||
|
||||
releases.ForEach(x => x.Monitored = false);
|
||||
toMonitor.Monitored = true;
|
||||
|
||||
Debug.Assert(!releases.Any() || releases.Count(x => x.Monitored) == 1, "one edition monitored");
|
||||
}
|
||||
|
||||
protected override bool RefreshChildren(SortedChildren localChildren, List<Edition> remoteChildren, Author remoteData, bool forceChildRefresh, bool forceUpdateFileTags, DateTime? lastUpdate)
|
||||
{
|
||||
// make sure only one of the releases ends up monitored
|
||||
localChildren.Old.ForEach(x => x.Monitored = false);
|
||||
MonitorSingleEdition(localChildren.Future);
|
||||
|
||||
localChildren.All.ForEach(x => _logger.Trace($"release: {x} monitored: {x.Monitored}"));
|
||||
|
||||
_editionService.InsertMany(localChildren.Added);
|
||||
|
||||
return _refreshEditionService.RefreshEditionInfo(localChildren.Added, localChildren.Updated, localChildren.Merged, localChildren.Deleted, localChildren.UpToDate, remoteChildren, forceUpdateFileTags);
|
||||
}
|
||||
|
||||
protected override void PublishEntityUpdatedEvent(Book entity)
|
||||
|
||||
59
src/NzbDrone.Core/Books/Services/RefreshEditionService.cs
Normal file
59
src/NzbDrone.Core/Books/Services/RefreshEditionService.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.Books
|
||||
{
|
||||
public interface IRefreshEditionService
|
||||
{
|
||||
bool RefreshEditionInfo(List<Edition> add, List<Edition> update, List<Tuple<Edition, Edition>> merge, List<Edition> delete, List<Edition> upToDate, List<Edition> remoteEditions, bool forceUpdateFileTags);
|
||||
}
|
||||
|
||||
public class RefreshEditionService : IRefreshEditionService
|
||||
{
|
||||
private readonly IEditionService _editionService;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RefreshEditionService(IEditionService editionService,
|
||||
IAudioTagService audioTagService,
|
||||
Logger logger)
|
||||
{
|
||||
_editionService = editionService;
|
||||
_audioTagService = audioTagService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public bool RefreshEditionInfo(List<Edition> add, List<Edition> update, List<Tuple<Edition, Edition>> merge, List<Edition> delete, List<Edition> upToDate, List<Edition> remoteEditions, bool forceUpdateFileTags)
|
||||
{
|
||||
var updateList = new List<Edition>();
|
||||
|
||||
// for editions that need updating, just grab the remote edition and set db ids
|
||||
foreach (var edition in update)
|
||||
{
|
||||
var remoteEdition = remoteEditions.Single(e => e.ForeignEditionId == edition.ForeignEditionId);
|
||||
edition.UseMetadataFrom(remoteEdition);
|
||||
|
||||
// make sure title is not null
|
||||
edition.Title = edition.Title ?? "Unknown";
|
||||
updateList.Add(edition);
|
||||
}
|
||||
|
||||
_editionService.DeleteMany(delete.Concat(merge.Select(x => x.Item1)).ToList());
|
||||
_editionService.UpdateMany(updateList);
|
||||
|
||||
var tagsToUpdate = updateList;
|
||||
if (forceUpdateFileTags)
|
||||
{
|
||||
_logger.Debug("Forcing tag update due to Author/Book/Edition updates");
|
||||
tagsToUpdate = updateList.Concat(upToDate).ToList();
|
||||
}
|
||||
|
||||
_audioTagService.SyncTags(tagsToUpdate);
|
||||
|
||||
return add.Any() || delete.Any() || updateList.Any() || merge.Any();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,13 +129,13 @@ namespace NzbDrone.Core.Books
|
||||
var existing = existingByAuthor.Concat(existingBySeries).GroupBy(x => x.ForeignSeriesId).Select(x => x.First()).ToList();
|
||||
|
||||
var books = _bookService.GetBooksByAuthorMetadataId(authorMetadataId);
|
||||
var bookDict = books.ToDictionary(x => x.ForeignWorkId);
|
||||
var bookDict = books.ToDictionary(x => x.ForeignBookId);
|
||||
var links = new List<SeriesBookLink>();
|
||||
|
||||
foreach (var s in remoteData.Series.Value)
|
||||
{
|
||||
s.LinkItems.Value.ForEach(x => x.Series = s);
|
||||
links.AddRange(s.LinkItems.Value.Where(x => bookDict.ContainsKey(x.Book.Value.ForeignWorkId)));
|
||||
links.AddRange(s.LinkItems.Value.Where(x => bookDict.ContainsKey(x.Book.Value.ForeignBookId)));
|
||||
}
|
||||
|
||||
var grouped = links.GroupBy(x => x.Series.Value);
|
||||
|
||||
@@ -53,12 +53,14 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
|
||||
Create.TableForModel("AuthorMetadata")
|
||||
.WithColumn("ForeignAuthorId").AsString().Unique()
|
||||
.WithColumn("GoodreadsId").AsInt32()
|
||||
.WithColumn("TitleSlug").AsString().Unique()
|
||||
.WithColumn("Name").AsString()
|
||||
.WithColumn("Overview").AsString().Nullable()
|
||||
.WithColumn("Disambiguation").AsString().Nullable()
|
||||
.WithColumn("Type").AsString().Nullable()
|
||||
.WithColumn("Gender").AsString().Nullable()
|
||||
.WithColumn("Hometown").AsString().Nullable()
|
||||
.WithColumn("Born").AsDateTime().Nullable()
|
||||
.WithColumn("Died").AsDateTime().Nullable()
|
||||
.WithColumn("Status").AsInt32()
|
||||
.WithColumn("Images").AsString()
|
||||
.WithColumn("Links").AsString().Nullable()
|
||||
@@ -68,31 +70,43 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
|
||||
Create.TableForModel("Books")
|
||||
.WithColumn("AuthorMetadataId").AsInt32().WithDefaultValue(0)
|
||||
.WithColumn("ForeignBookId").AsString().Unique()
|
||||
.WithColumn("ForeignWorkId").AsString().Indexed()
|
||||
.WithColumn("GoodreadsId").AsInt32()
|
||||
.WithColumn("ForeignBookId").AsString().Indexed()
|
||||
.WithColumn("TitleSlug").AsString().Unique()
|
||||
.WithColumn("Isbn13").AsString().Nullable()
|
||||
.WithColumn("Asin").AsString().Nullable()
|
||||
.WithColumn("Title").AsString()
|
||||
.WithColumn("Language").AsString().Nullable()
|
||||
.WithColumn("Overview").AsString().Nullable()
|
||||
.WithColumn("PageCount").AsInt32().Nullable()
|
||||
.WithColumn("Disambiguation").AsString().Nullable()
|
||||
.WithColumn("Publisher").AsString().Nullable()
|
||||
.WithColumn("ReleaseDate").AsDateTime().Nullable()
|
||||
.WithColumn("Images").AsString()
|
||||
.WithColumn("Links").AsString().Nullable()
|
||||
.WithColumn("Genres").AsString().Nullable()
|
||||
.WithColumn("Ratings").AsString().Nullable()
|
||||
.WithColumn("CleanTitle").AsString().Indexed()
|
||||
.WithColumn("Monitored").AsBoolean()
|
||||
.WithColumn("AnyEditionOk").AsBoolean()
|
||||
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
||||
.WithColumn("Added").AsDateTime().Nullable()
|
||||
.WithColumn("AddOptions").AsString().Nullable();
|
||||
|
||||
Create.TableForModel("Editions")
|
||||
.WithColumn("BookId").AsInt32().WithDefaultValue(0)
|
||||
.WithColumn("ForeignEditionId").AsString().Unique()
|
||||
.WithColumn("Isbn13").AsString().Nullable()
|
||||
.WithColumn("Asin").AsString().Nullable()
|
||||
.WithColumn("Title").AsString()
|
||||
.WithColumn("TitleSlug").AsString()
|
||||
.WithColumn("Language").AsString().Nullable()
|
||||
.WithColumn("Overview").AsString().Nullable()
|
||||
.WithColumn("Format").AsString().Nullable()
|
||||
.WithColumn("IsEbook").AsBoolean().Nullable()
|
||||
.WithColumn("Disambiguation").AsString().Nullable()
|
||||
.WithColumn("Publisher").AsString().Nullable()
|
||||
.WithColumn("PageCount").AsInt32().Nullable()
|
||||
.WithColumn("ReleaseDate").AsDateTime().Nullable()
|
||||
.WithColumn("Images").AsString()
|
||||
.WithColumn("Links").AsString().Nullable()
|
||||
.WithColumn("Ratings").AsString().Nullable()
|
||||
.WithColumn("Monitored").AsBoolean()
|
||||
.WithColumn("ManualAdd").AsBoolean();
|
||||
|
||||
Create.TableForModel("BookFiles")
|
||||
.WithColumn("BookId").AsInt32().Indexed()
|
||||
.WithColumn("EditionId").AsInt32().Indexed()
|
||||
.WithColumn("CalibreId").AsInt32()
|
||||
.WithColumn("Quality").AsString()
|
||||
.WithColumn("Size").AsInt64()
|
||||
@@ -152,8 +166,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
|
||||
Create.TableForModel("MetadataProfiles")
|
||||
.WithColumn("Name").AsString().Unique()
|
||||
.WithColumn("MinRating").AsDouble()
|
||||
.WithColumn("MinRatingCount").AsInt32()
|
||||
.WithColumn("MinPopularity").AsDouble()
|
||||
.WithColumn("SkipMissingDate").AsBoolean()
|
||||
.WithColumn("SkipMissingIsbn").AsBoolean()
|
||||
.WithColumn("SkipPartsAndSets").AsBoolean()
|
||||
|
||||
@@ -123,9 +123,12 @@ namespace NzbDrone.Core.Datastore
|
||||
.HasOne(r => r.AuthorMetadata, r => r.AuthorMetadataId)
|
||||
.LazyLoad(x => x.BookFiles,
|
||||
(db, book) => db.Query<BookFile>(new SqlBuilder()
|
||||
.Join<BookFile, Book>((l, r) => l.BookId == r.Id)
|
||||
.Join<BookFile, Book>((l, r) => l.EditionId == r.Id)
|
||||
.Where<Book>(b => b.Id == book.Id)).ToList(),
|
||||
b => b.Id > 0)
|
||||
.LazyLoad(x => x.Editions,
|
||||
(db, book) => db.Query<Edition>(new SqlBuilder().Where<Edition>(e => e.BookId == book.Id)).ToList(),
|
||||
b => b.Id > 0)
|
||||
.LazyLoad(a => a.Author,
|
||||
(db, book) => AuthorRepository.Query(db,
|
||||
new SqlBuilder()
|
||||
@@ -133,14 +136,22 @@ namespace NzbDrone.Core.Datastore
|
||||
.Where<Author>(a => a.AuthorMetadataId == book.AuthorMetadataId)).SingleOrDefault(),
|
||||
a => a.AuthorMetadataId > 0);
|
||||
|
||||
Mapper.Entity<Edition>("Editions").RegisterModel()
|
||||
.HasOne(r => r.Book, r => r.BookId)
|
||||
.LazyLoad(x => x.BookFiles,
|
||||
(db, book) => db.Query<BookFile>(new SqlBuilder()
|
||||
.Join<BookFile, Book>((l, r) => l.EditionId == r.Id)
|
||||
.Where<Book>(b => b.Id == book.Id)).ToList(),
|
||||
b => b.Id > 0);
|
||||
|
||||
Mapper.Entity<BookFile>("BookFiles").RegisterModel()
|
||||
.HasOne(f => f.Book, f => f.BookId)
|
||||
.HasOne(f => f.Edition, f => f.EditionId)
|
||||
.LazyLoad(x => x.Author,
|
||||
(db, f) => AuthorRepository.Query(db,
|
||||
new SqlBuilder()
|
||||
.Join<Author, AuthorMetadata>((a, m) => a.AuthorMetadataId == m.Id)
|
||||
.Join<Author, Book>((l, r) => l.AuthorMetadataId == r.AuthorMetadataId)
|
||||
.Where<Book>(a => a.Id == f.BookId)).SingleOrDefault(),
|
||||
.Where<Book>(a => a.Id == f.EditionId)).SingleOrDefault(),
|
||||
t => t.Id > 0);
|
||||
|
||||
Mapper.Entity<QualityDefinition>("QualityDefinitions").RegisterModel()
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace NzbDrone.Core.Extras
|
||||
public void Handle(TrackFolderCreatedEvent message)
|
||||
{
|
||||
var author = message.Author;
|
||||
var book = _bookService.GetBook(message.BookFile.BookId);
|
||||
var book = _bookService.GetBook(message.BookFile.EditionId);
|
||||
|
||||
foreach (var extraFileManager in _extraFileManagers)
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Extras.Files
|
||||
return new TExtraFile
|
||||
{
|
||||
AuthorId = author.Id,
|
||||
BookId = bookFile.BookId,
|
||||
BookId = bookFile.EditionId,
|
||||
BookFileId = bookFile.Id,
|
||||
RelativePath = author.Path.GetRelativePath(newFileName),
|
||||
Extension = extension
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace NzbDrone.Core.Extras.Metadata
|
||||
foreach (var filePath in distinctTrackFilePaths)
|
||||
{
|
||||
var metadataFilesForConsumer = GetMetadataFilesForConsumer(consumer, metadataFiles)
|
||||
.Where(m => m.BookId == filePath.BookId)
|
||||
.Where(m => m.BookId == filePath.EditionId)
|
||||
.Where(m => m.Type == MetadataType.BookImage || m.Type == MetadataType.BookMetadata)
|
||||
.ToList();
|
||||
|
||||
@@ -287,7 +287,7 @@ namespace NzbDrone.Core.Extras.Metadata
|
||||
new MetadataFile
|
||||
{
|
||||
AuthorId = author.Id,
|
||||
BookId = bookFile.BookId,
|
||||
BookId = bookFile.EditionId,
|
||||
BookFileId = bookFile.Id,
|
||||
Consumer = consumer.GetType().Name,
|
||||
Type = MetadataType.BookMetadata,
|
||||
|
||||
@@ -294,7 +294,7 @@ namespace NzbDrone.Core.History
|
||||
Quality = message.BookFile.Quality,
|
||||
SourceTitle = message.BookFile.Path,
|
||||
AuthorId = message.BookFile.Author.Value.Id,
|
||||
BookId = message.BookFile.BookId
|
||||
BookId = message.BookFile.EditionId
|
||||
};
|
||||
|
||||
history.Data.Add("Reason", message.Reason.ToString());
|
||||
@@ -314,7 +314,7 @@ namespace NzbDrone.Core.History
|
||||
Quality = message.BookFile.Quality,
|
||||
SourceTitle = message.OriginalPath,
|
||||
AuthorId = message.BookFile.Author.Value.Id,
|
||||
BookId = message.BookFile.BookId
|
||||
BookId = message.BookFile.EditionId
|
||||
};
|
||||
|
||||
history.Data.Add("SourcePath", sourcePath);
|
||||
@@ -334,7 +334,7 @@ namespace NzbDrone.Core.History
|
||||
Quality = message.BookFile.Quality,
|
||||
SourceTitle = path,
|
||||
AuthorId = message.BookFile.Author.Value.Id,
|
||||
BookId = message.BookFile.BookId
|
||||
BookId = message.BookFile.EditionId
|
||||
};
|
||||
|
||||
history.Data.Add("TagsScrubbed", message.Scrubbed.ToString());
|
||||
|
||||
@@ -18,12 +18,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
// Unlink where track no longer exists
|
||||
mapper.Execute(@"UPDATE BookFiles
|
||||
SET BookId = 0
|
||||
SET EditionId = 0
|
||||
WHERE Id IN (
|
||||
SELECT BookFiles.Id FROM BookFiles
|
||||
LEFT OUTER JOIN Books
|
||||
ON BookFiles.BookId = Books.Id
|
||||
WHERE Books.Id IS NULL)");
|
||||
LEFT OUTER JOIN Editions
|
||||
ON BookFiles.EditionId = Editions.Id
|
||||
WHERE Editions.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace NzbDrone.Core.ImportLists
|
||||
|
||||
if (report.AlbumMusicBrainzId.IsNotNullOrWhiteSpace() && int.TryParse(report.AlbumMusicBrainzId, out var goodreadsId))
|
||||
{
|
||||
mappedAlbum = _bookSearchService.SearchByGoodreadsId(goodreadsId).FirstOrDefault(x => x.GoodreadsId == goodreadsId);
|
||||
mappedAlbum = _bookSearchService.SearchByGoodreadsId(goodreadsId).FirstOrDefault(x => int.TryParse(x.ForeignBookId, out var bookId) && bookId == goodreadsId);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -72,17 +72,13 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
var searchSpec = Get<BookSearchCriteria>(author, new List<Book> { book }, userInvokedSearch, interactiveSearch);
|
||||
|
||||
searchSpec.BookTitle = book.Title;
|
||||
searchSpec.BookIsbn = book.Isbn13;
|
||||
|
||||
// searchSpec.BookIsbn = book.Isbn13;
|
||||
if (book.ReleaseDate.HasValue)
|
||||
{
|
||||
searchSpec.BookYear = book.ReleaseDate.Value.Year;
|
||||
}
|
||||
|
||||
if (book.Disambiguation.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
searchSpec.Disambiguation = book.Disambiguation;
|
||||
}
|
||||
|
||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
}
|
||||
}
|
||||
|
||||
private bool SupportsAudioSearch
|
||||
private bool SupportsBookSearch
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -67,7 +67,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
if (SupportsAudioSearch)
|
||||
if (SupportsBookSearch)
|
||||
{
|
||||
AddBookPageableRequests(pageableRequests,
|
||||
searchCriteria,
|
||||
@@ -78,12 +78,17 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
pageableRequests.AddTier();
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(MaxPages,
|
||||
/* pageableRequests.Add(GetPagedRequests(MaxPages,
|
||||
Settings.Categories,
|
||||
"search",
|
||||
NewsnabifyTitle($"&q={searchCriteria.BookIsbn}")));
|
||||
|
||||
pageableRequests.AddTier();
|
||||
pageableRequests.AddTier();*/
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(MaxPages,
|
||||
Settings.Categories,
|
||||
"search",
|
||||
NewsnabifyTitle($"&q={searchCriteria.BookQuery}+{searchCriteria.AuthorQuery}")));
|
||||
|
||||
pageableRequests.Add(GetPagedRequests(MaxPages,
|
||||
Settings.Categories,
|
||||
@@ -98,7 +103,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
if (SupportsAudioSearch)
|
||||
if (SupportsBookSearch)
|
||||
{
|
||||
AddBookPageableRequests(pageableRequests,
|
||||
searchCriteria,
|
||||
@@ -122,7 +127,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
chain.AddTier();
|
||||
|
||||
chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "book", $"&q={parameters}"));
|
||||
chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "book", $"{parameters}"));
|
||||
}
|
||||
|
||||
private IEnumerable<IndexerRequest> GetPagedRequests(int maxPages, IEnumerable<int> categories, string searchType, string parameters)
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace NzbDrone.Core.MediaCover
|
||||
|
||||
if (coverEntity == MediaCoverEntity.Book)
|
||||
{
|
||||
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/Albums/" + entityId + "/" + mediaCover.CoverType.ToString().ToLower() + mediaCover.Extension;
|
||||
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/Books/" + entityId + "/" + mediaCover.CoverType.ToString().ToLower() + mediaCover.Extension;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -113,7 +113,7 @@ namespace NzbDrone.Core.MediaCover
|
||||
|
||||
private string GetAlbumCoverPath(int bookId)
|
||||
{
|
||||
return Path.Combine(_coverRootFolder, "Albums", bookId.ToString());
|
||||
return Path.Combine(_coverRootFolder, "Books", bookId.ToString());
|
||||
}
|
||||
|
||||
private void EnsureArtistCovers(Author author)
|
||||
@@ -163,7 +163,7 @@ namespace NzbDrone.Core.MediaCover
|
||||
|
||||
public void EnsureAlbumCovers(Book book)
|
||||
{
|
||||
foreach (var cover in book.Images.Where(e => e.CoverType == MediaCoverTypes.Cover))
|
||||
foreach (var cover in book.Editions.Value.Single(x => x.Monitored).Images.Where(e => e.CoverType == MediaCoverTypes.Cover))
|
||||
{
|
||||
var fileName = GetCoverPath(book.Id, MediaCoverEntity.Book, cover.CoverType, cover.Extension, null);
|
||||
var alreadyExists = false;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
ParsedTrackInfo ReadTags(string file);
|
||||
void WriteTags(BookFile trackfile, bool newDownload, bool force = false);
|
||||
void SyncTags(List<Book> tracks);
|
||||
void SyncTags(List<Edition> tracks);
|
||||
List<RetagBookFilePreview> GetRetagPreviewsByArtist(int authorId);
|
||||
List<RetagBookFilePreview> GetRetagPreviewsByAlbum(int authorId);
|
||||
}
|
||||
@@ -148,7 +148,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
_eventAggregator.PublishEvent(new BookFileRetaggedEvent(trackfile.Author.Value, trackfile, diff, _configService.ScrubAudioTags));
|
||||
}
|
||||
|
||||
public void SyncTags(List<Book> books)
|
||||
public void SyncTags(List<Edition> editions)
|
||||
{
|
||||
if (_configService.WriteAudioTags != WriteAudioTagsType.Sync)
|
||||
{
|
||||
@@ -156,9 +156,9 @@ namespace NzbDrone.Core.MediaFiles
|
||||
}
|
||||
|
||||
// get the tracks to update
|
||||
foreach (var book in books)
|
||||
foreach (var edition in editions)
|
||||
{
|
||||
var bookFiles = book.BookFiles.Value;
|
||||
var bookFiles = edition.BookFiles.Value;
|
||||
|
||||
_logger.Debug($"Syncing audio tags for {bookFiles.Count} files");
|
||||
|
||||
@@ -166,7 +166,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
// populate tracks (which should also have release/book/author set) because
|
||||
// not all of the updates will have been committed to the database yet
|
||||
file.Book = book;
|
||||
file.Edition = edition;
|
||||
WriteTags(file, false);
|
||||
}
|
||||
}
|
||||
@@ -188,11 +188,11 @@ namespace NzbDrone.Core.MediaFiles
|
||||
|
||||
private IEnumerable<RetagBookFilePreview> GetPreviews(List<BookFile> files)
|
||||
{
|
||||
foreach (var f in files.OrderBy(x => x.Book.Value.Title))
|
||||
foreach (var f in files.OrderBy(x => x.Edition.Value.Title))
|
||||
{
|
||||
var file = f;
|
||||
|
||||
if (f.Book.Value == null)
|
||||
if (f.Edition.Value == null)
|
||||
{
|
||||
_logger.Warn($"File {f} is not linked to any books");
|
||||
continue;
|
||||
@@ -207,7 +207,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
yield return new RetagBookFilePreview
|
||||
{
|
||||
AuthorId = file.Author.Value.Id,
|
||||
BookId = file.Book.Value.Id,
|
||||
BookId = file.Edition.Value.Id,
|
||||
BookFileId = file.Id,
|
||||
Path = file.Path,
|
||||
Changes = diff
|
||||
|
||||
@@ -19,12 +19,12 @@ namespace NzbDrone.Core.MediaFiles
|
||||
public string ReleaseGroup { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public MediaInfoModel MediaInfo { get; set; }
|
||||
public int BookId { get; set; }
|
||||
public int EditionId { get; set; }
|
||||
public int CalibreId { get; set; }
|
||||
|
||||
// These are queried from the database
|
||||
public LazyLoaded<Author> Author { get; set; }
|
||||
public LazyLoaded<Book> Book { get; set; }
|
||||
public LazyLoaded<Edition> Edition { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -60,9 +60,9 @@ namespace NzbDrone.Core.MediaFiles
|
||||
|
||||
public BookFile MoveBookFile(BookFile bookFile, Author author)
|
||||
{
|
||||
var book = _bookService.GetBook(bookFile.BookId);
|
||||
var newFileName = _buildFileNames.BuildBookFileName(author, book, bookFile);
|
||||
var filePath = _buildFileNames.BuildBookFilePath(author, book, newFileName, Path.GetExtension(bookFile.Path));
|
||||
var book = _bookService.GetBook(bookFile.EditionId);
|
||||
var newFileName = _buildFileNames.BuildBookFileName(author, bookFile.Edition.Value, bookFile);
|
||||
var filePath = _buildFileNames.BuildBookFilePath(author, bookFile.Edition.Value, newFileName, Path.GetExtension(bookFile.Path));
|
||||
|
||||
EnsureBookFolder(bookFile, author, book, filePath);
|
||||
|
||||
@@ -73,8 +73,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||
|
||||
public BookFile MoveBookFile(BookFile bookFile, LocalBook localBook)
|
||||
{
|
||||
var newFileName = _buildFileNames.BuildBookFileName(localBook.Author, localBook.Book, bookFile);
|
||||
var filePath = _buildFileNames.BuildBookFilePath(localBook.Author, localBook.Book, newFileName, Path.GetExtension(localBook.Path));
|
||||
var newFileName = _buildFileNames.BuildBookFileName(localBook.Author, localBook.Edition, bookFile);
|
||||
var filePath = _buildFileNames.BuildBookFilePath(localBook.Author, localBook.Edition, newFileName, Path.GetExtension(localBook.Path));
|
||||
|
||||
EnsureTrackFolder(bookFile, localBook, filePath);
|
||||
|
||||
@@ -85,8 +85,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||
|
||||
public BookFile CopyBookFile(BookFile bookFile, LocalBook localBook)
|
||||
{
|
||||
var newFileName = _buildFileNames.BuildBookFileName(localBook.Author, localBook.Book, bookFile);
|
||||
var filePath = _buildFileNames.BuildBookFilePath(localBook.Author, localBook.Book, newFileName, Path.GetExtension(localBook.Path));
|
||||
var newFileName = _buildFileNames.BuildBookFileName(localBook.Author, localBook.Edition, bookFile);
|
||||
var filePath = _buildFileNames.BuildBookFilePath(localBook.Author, localBook.Edition, newFileName, Path.GetExtension(localBook.Path));
|
||||
|
||||
EnsureTrackFolder(bookFile, localBook, filePath);
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
private void EnsureBookFolder(BookFile bookFile, Author author, Book book, string filePath)
|
||||
{
|
||||
var trackFolder = Path.GetDirectoryName(filePath);
|
||||
var bookFolder = _buildFileNames.BuildBookPath(author, book);
|
||||
var bookFolder = _buildFileNames.BuildBookPath(author);
|
||||
var authorFolder = author.Path;
|
||||
var rootFolder = new OsPath(authorFolder).Directory.FullPath;
|
||||
|
||||
|
||||
@@ -11,18 +11,18 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Aggregation
|
||||
public interface IAugmentingService
|
||||
{
|
||||
LocalBook Augment(LocalBook localTrack, bool otherFiles);
|
||||
LocalAlbumRelease Augment(LocalAlbumRelease localAlbum);
|
||||
LocalEdition Augment(LocalEdition localAlbum);
|
||||
}
|
||||
|
||||
public class AugmentingService : IAugmentingService
|
||||
{
|
||||
private readonly IEnumerable<IAggregate<LocalBook>> _trackAugmenters;
|
||||
private readonly IEnumerable<IAggregate<LocalAlbumRelease>> _albumAugmenters;
|
||||
private readonly IEnumerable<IAggregate<LocalEdition>> _albumAugmenters;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AugmentingService(IEnumerable<IAggregate<LocalBook>> trackAugmenters,
|
||||
IEnumerable<IAggregate<LocalAlbumRelease>> albumAugmenters,
|
||||
IEnumerable<IAggregate<LocalEdition>> albumAugmenters,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
{
|
||||
@@ -61,7 +61,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Aggregation
|
||||
return localTrack;
|
||||
}
|
||||
|
||||
public LocalAlbumRelease Augment(LocalAlbumRelease localAlbum)
|
||||
public LocalEdition Augment(LocalEdition localAlbum)
|
||||
{
|
||||
foreach (var augmenter in _albumAugmenters)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.BookImport.Aggregation.Aggregators
|
||||
{
|
||||
public class AggregateFilenameInfo : IAggregate<LocalAlbumRelease>
|
||||
public class AggregateFilenameInfo : IAggregate<LocalEdition>
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Aggregation.Aggregators
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public LocalAlbumRelease Aggregate(LocalAlbumRelease release, bool others)
|
||||
public LocalEdition Aggregate(LocalEdition release, bool others)
|
||||
{
|
||||
var tracks = release.LocalBooks;
|
||||
if (tracks.Count(x => x.FileTrackInfo.Title.IsNullOrWhiteSpace()) > 0
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Books;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.BookImport.Identification
|
||||
{
|
||||
public class CandidateAlbumRelease
|
||||
{
|
||||
public CandidateAlbumRelease()
|
||||
{
|
||||
}
|
||||
|
||||
public CandidateAlbumRelease(Book book)
|
||||
{
|
||||
Book = book;
|
||||
ExistingTracks = new List<BookFile>();
|
||||
}
|
||||
|
||||
public Book Book { get; set; }
|
||||
public List<BookFile> ExistingTracks { get; set; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user