Compare commits

..

11 Commits

Author SHA1 Message Date
Mark McDowall
102481ed60 Fixed: Import queue not processing after incomplete import
(cherry picked from commit 3afae968ebc72ad35abe07cfc4b84d7e243b1840)
2024-07-15 19:08:22 +00:00
Bogdan
2fb1b8af20 Bump version to 0.3.32 2024-07-14 12:31:38 +03:00
Bogdan
af1f389f8e Fixed: Validate metadata and quality profiles for root folders
Don't allow `0` as possible value for metadata and quality profiles, and permit to edit root folders with bad values in UI.
2024-07-12 16:37:16 +03:00
Bogdan
b5334da253 Fixed: Creating root folders without default tags 2024-07-12 16:32:19 +03:00
Bogdan
68b3904382 Bump version to 0.3.31 2024-07-07 21:55:01 +03:00
Bogdan
c8b09b9e29 Fixed: Already imported downloads appearing in Queue briefly
(cherry picked from commit 8099ba10afded446779290de29b1baaf0be932c3)

Closes #3538
2024-07-01 08:45:01 +03:00
Bogdan
d910fc42ab Bump mac image to 12 2024-06-26 23:51:49 +03:00
Mark McDowall
a6db8bfe0e New: Ignore Deluge torrents without a title
(cherry picked from commit a0d29331341320268552660658b949179c963793)
2024-06-26 02:46:53 +03:00
Bogdan
2033d7e411 Fixed: Exclude invalid releases from Newznab and Torznab parsers
(cherry picked from commit fb060730c7d52cd342484dc68595698a9430df7b)
2024-06-26 02:46:39 +03:00
dependabot[bot]
4a04e54ceb Bump ws from 7.5.9 to 7.5.10
Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-23 22:41:14 +03:00
Bogdan
d57a9ab9b0 Bump version to 0.3.30 2024-06-23 21:53:58 +03:00
20 changed files with 90 additions and 64 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.3.29' majorVersion: '0.3.32'
minorVersion: $[counter('minorVersion', 1)] minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)' readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)' buildName: '$(Build.SourceBranchName).$(readarrVersion)'
@@ -20,7 +20,7 @@ variables:
innoVersion: '6.2.0' innoVersion: '6.2.0'
windowsImage: 'windows-2022' windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04' linuxImage: 'ubuntu-20.04'
macImage: 'macOS-11' macImage: 'macOS-12'
trigger: trigger:
branches: branches:

View File

@@ -8,6 +8,7 @@ import TagListConnector from 'Components/TagListConnector';
import { createMetadataProfileSelectorForHook } from 'Store/Selectors/createMetadataProfileSelector'; import { createMetadataProfileSelectorForHook } from 'Store/Selectors/createMetadataProfileSelector';
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector'; import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
import { SelectStateInputProps } from 'typings/props'; import { SelectStateInputProps } from 'typings/props';
import translate from 'Utilities/String/translate';
import styles from './ManageImportListsModalRow.css'; import styles from './ManageImportListsModalRow.css';
interface ManageImportListsModalRowProps { interface ManageImportListsModalRowProps {
@@ -70,7 +71,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.qualityProfileId}> <TableRowCell className={styles.qualityProfileId}>
{qualityProfile?.name ?? 'None'} {qualityProfile?.name ?? translate('None')}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.metadataProfileId}> <TableRowCell className={styles.metadataProfileId}>
@@ -82,7 +83,7 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.enableAutomaticAdd}> <TableRowCell className={styles.enableAutomaticAdd}>
{enableAutomaticAdd ? 'Yes' : 'No'} {enableAutomaticAdd ? translate('Yes') : translate('No')}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.tags}> <TableRowCell className={styles.tags}>

View File

@@ -75,12 +75,12 @@ class RootFolder extends Component {
{path} {path}
</Label> </Label>
<Label kind={kinds.SUCCESS}> <Label kind={qualityProfile?.name ? kinds.SUCCESS : kinds.DANGER}>
{qualityProfile.name} {qualityProfile?.name || translate('None')}
</Label> </Label>
<Label kind={kinds.SUCCESS}> <Label kind={metadataProfile?.name ? kinds.SUCCESS : kinds.DANGER}>
{metadataProfile.name} {metadataProfile?.name || translate('None')}
</Label> </Label>
</div> </div>

View File

@@ -46,6 +46,7 @@ namespace NzbDrone.Core.Test.QueueTests
_trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1) _trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1)
.All() .All()
.With(v => v.IsTrackable = true)
.With(v => v.DownloadItem = downloadItem) .With(v => v.DownloadItem = downloadItem)
.With(v => v.RemoteBook = remoteBook) .With(v => v.RemoteBook = remoteBook)
.Build() .Build()

View File

@@ -15,18 +15,18 @@ namespace NzbDrone.Core.Books
public class BookCutoffService : IBookCutoffService public class BookCutoffService : IBookCutoffService
{ {
private readonly IBookRepository _bookRepository; private readonly IBookRepository _bookRepository;
private readonly IProfileService _profileService; private readonly IQualityProfileService _qualityProfileService;
public BookCutoffService(IBookRepository bookRepository, IProfileService profileService) public BookCutoffService(IBookRepository bookRepository, IQualityProfileService qualityProfileService)
{ {
_bookRepository = bookRepository; _bookRepository = bookRepository;
_profileService = profileService; _qualityProfileService = qualityProfileService;
} }
public PagingSpec<Book> BooksWhereCutoffUnmet(PagingSpec<Book> pagingSpec) public PagingSpec<Book> BooksWhereCutoffUnmet(PagingSpec<Book> pagingSpec)
{ {
var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>(); var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>();
var profiles = _profileService.All(); var profiles = _qualityProfileService.All();
//Get all items less than the cutoff //Get all items less than the cutoff
foreach (var profile in profiles) foreach (var profile in profiles)

View File

@@ -122,14 +122,23 @@ namespace NzbDrone.Core.Download.Clients.Deluge
} }
var items = new List<DownloadClientItem>(); var items = new List<DownloadClientItem>();
var ignoredCount = 0;
foreach (var torrent in torrents) foreach (var torrent in torrents)
{ {
if (torrent.Hash == null) // Silently ignore torrents with no hash
if (torrent.Hash.IsNullOrWhiteSpace())
{ {
continue; continue;
} }
// Ignore torrents without a name, but track to log a single warning for all invalid torrents.
if (torrent.Name.IsNullOrWhiteSpace())
{
ignoredCount++;
continue;
}
var item = new DownloadClientItem(); var item = new DownloadClientItem();
item.DownloadId = torrent.Hash.ToUpper(); item.DownloadId = torrent.Hash.ToUpper();
item.Title = torrent.Name; item.Title = torrent.Name;
@@ -187,6 +196,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge
items.Add(item); items.Add(item);
} }
if (ignoredCount > 0)
{
_logger.Warn("{0} torrent(s) were ignored becuase they did not have a title, check Deluge and remove any invalid torrents");
}
return items; return items;
} }

View File

@@ -173,6 +173,9 @@ namespace NzbDrone.Core.Download
.Log(); .Log();
} }
var episodes = _episodeService.GetEpisodes(trackedDownload.RemoteEpisode.Episodes.Select(e => e.Id));
var files = _mediaFileService.GetFiles(episodes.Select(e => e.EpisodeFileId).Where(i => i > 0).Distinct());
trackedDownload.State = TrackedDownloadState.Imported; trackedDownload.State = TrackedDownloadState.Imported;
var importedAuthorId = historyItems.Where(x => x.EventType == EntityHistoryEventType.BookFileImported) var importedAuthorId = historyItems.Where(x => x.EventType == EntityHistoryEventType.BookFileImported)

View File

@@ -68,16 +68,17 @@ namespace NzbDrone.Core.Indexers.Newznab
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases) protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
{ {
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray(); var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty()) if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
{ {
if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any()) if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any())
{ {
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Torznab indexer?", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]); _logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Torznab indexer?", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
return false;
} }
else
{ _logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
}
} }
return true; return true;

View File

@@ -268,26 +268,26 @@ namespace NzbDrone.Core.Indexers
protected virtual RssEnclosure[] GetEnclosures(XElement item) protected virtual RssEnclosure[] GetEnclosures(XElement item)
{ {
var enclosures = item.Elements("enclosure") var enclosures = item.Elements("enclosure")
.Select(v => .Select(v =>
{ {
try try
{ {
return new RssEnclosure return new RssEnclosure
{ {
Url = v.Attribute("url")?.Value, Url = v.Attribute("url")?.Value,
Type = v.Attribute("type")?.Value, Type = v.Attribute("type")?.Value,
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0 Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
}; };
} }
catch (Exception e) catch (Exception ex)
{ {
_logger.Warn(e, "Failed to get enclosure for: {0}", item.Title()); _logger.Warn(ex, "Failed to get enclosure for: {0}", item.Title());
} }
return null; return null;
}) })
.Where(v => v != null) .Where(v => v != null)
.ToArray(); .ToArray();
return enclosures; return enclosures;
} }

View File

@@ -59,16 +59,17 @@ namespace NzbDrone.Core.Indexers.Torznab
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases) protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
{ {
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray(); var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty()) if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
{ {
if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any()) if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any())
{ {
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Newznab indexer?", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]); _logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Newznab indexer?", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
return false;
} }
else
{ _logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
}
} }
return true; return true;

View File

@@ -53,7 +53,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport
private readonly IAugmentingService _augmentingService; private readonly IAugmentingService _augmentingService;
private readonly IIdentificationService _identificationService; private readonly IIdentificationService _identificationService;
private readonly IRootFolderService _rootFolderService; private readonly IRootFolderService _rootFolderService;
private readonly IProfileService _qualityProfileService; private readonly IQualityProfileService _qualityProfileService;
private readonly Logger _logger; private readonly Logger _logger;
public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification<LocalBook>> trackSpecifications, public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification<LocalBook>> trackSpecifications,
@@ -63,7 +63,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport
IAugmentingService augmentingService, IAugmentingService augmentingService,
IIdentificationService identificationService, IIdentificationService identificationService,
IRootFolderService rootFolderService, IRootFolderService rootFolderService,
IProfileService qualityProfileService, IQualityProfileService qualityProfileService,
Logger logger) Logger logger)
{ {
_trackSpecifications = trackSpecifications; _trackSpecifications = trackSpecifications;

View File

@@ -13,7 +13,7 @@ using NzbDrone.Core.RootFolders;
namespace NzbDrone.Core.Profiles.Qualities namespace NzbDrone.Core.Profiles.Qualities
{ {
public interface IProfileService public interface IQualityProfileService
{ {
QualityProfile Add(QualityProfile profile); QualityProfile Add(QualityProfile profile);
void Update(QualityProfile profile); void Update(QualityProfile profile);
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Profiles.Qualities
QualityProfile GetDefaultProfile(string name, Quality cutoff = null, params Quality[] allowed); QualityProfile GetDefaultProfile(string name, Quality cutoff = null, params Quality[] allowed);
} }
public class QualityProfileService : IProfileService, public class QualityProfileService : IQualityProfileService,
IHandle<ApplicationStartedEvent>, IHandle<ApplicationStartedEvent>,
IHandle<CustomFormatAddedEvent>, IHandle<CustomFormatAddedEvent>,
IHandle<CustomFormatDeletedEvent> IHandle<CustomFormatDeletedEvent>

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Queue
public class QueueService : IQueueService, IHandle<TrackedDownloadRefreshedEvent> public class QueueService : IQueueService, IHandle<TrackedDownloadRefreshedEvent>
{ {
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private static List<Queue> _queue = new List<Queue>(); private static List<Queue> _queue = new ();
private readonly IHistoryService _historyService; private readonly IHistoryService _historyService;
public QueueService(IEventAggregator eventAggregator, public QueueService(IEventAggregator eventAggregator,
@@ -105,8 +105,11 @@ namespace NzbDrone.Core.Queue
public void Handle(TrackedDownloadRefreshedEvent message) public void Handle(TrackedDownloadRefreshedEvent message)
{ {
_queue = message.TrackedDownloads.OrderBy(c => c.DownloadItem.RemainingTime).SelectMany(MapQueue) _queue = message.TrackedDownloads
.ToList(); .Where(t => t.IsTrackable)
.OrderBy(c => c.DownloadItem.RemainingTime)
.SelectMany(MapQueue)
.ToList();
_eventAggregator.PublishEvent(new QueueUpdatedEvent()); _eventAggregator.PublishEvent(new QueueUpdatedEvent());
} }

View File

@@ -13,7 +13,7 @@ namespace NzbDrone.Core.RootFolders
public int DefaultQualityProfileId { get; set; } public int DefaultQualityProfileId { get; set; }
public MonitorTypes DefaultMonitorOption { get; set; } public MonitorTypes DefaultMonitorOption { get; set; }
public NewItemMonitorTypes DefaultNewItemMonitorOption { get; set; } public NewItemMonitorTypes DefaultNewItemMonitorOption { get; set; }
public HashSet<int> DefaultTags { get; set; } public HashSet<int> DefaultTags { get; set; } = new ();
public bool IsCalibreLibrary { get; set; } public bool IsCalibreLibrary { get; set; }
public CalibreSettings CalibreSettings { get; set; } public CalibreSettings CalibreSettings { get; set; }

View File

@@ -5,11 +5,11 @@ namespace NzbDrone.Core.Validation
{ {
public class QualityProfileExistsValidator : PropertyValidator public class QualityProfileExistsValidator : PropertyValidator
{ {
private readonly IProfileService _profileService; private readonly IQualityProfileService _qualityProfileService;
public QualityProfileExistsValidator(IProfileService profileService) public QualityProfileExistsValidator(IQualityProfileService qualityProfileService)
{ {
_profileService = profileService; _qualityProfileService = qualityProfileService;
} }
protected override string GetDefaultMessageTemplate() => "Quality Profile does not exist"; protected override string GetDefaultMessageTemplate() => "Quality Profile does not exist";
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Validation
return true; return true;
} }
return _profileService.Exists((int)context.PropertyValue); return _qualityProfileService.Exists((int)context.PropertyValue);
} }
} }
} }

View File

@@ -14,10 +14,10 @@ namespace Readarr.Api.V1.Profiles.Quality
[V1ApiController] [V1ApiController]
public class QualityProfileController : RestController<QualityProfileResource> public class QualityProfileController : RestController<QualityProfileResource>
{ {
private readonly IProfileService _qualityProfileService; private readonly IQualityProfileService _qualityProfileService;
private readonly ICustomFormatService _formatService; private readonly ICustomFormatService _formatService;
public QualityProfileController(IProfileService qualityProfileService, ICustomFormatService formatService) public QualityProfileController(IQualityProfileService qualityProfileService, ICustomFormatService formatService)
{ {
_qualityProfileService = qualityProfileService; _qualityProfileService = qualityProfileService;
_formatService = formatService; _formatService = formatService;

View File

@@ -7,17 +7,17 @@ namespace Readarr.Api.V1.Profiles.Quality
[V1ApiController("qualityprofile/schema")] [V1ApiController("qualityprofile/schema")]
public class QualityProfileSchemaController : Controller public class QualityProfileSchemaController : Controller
{ {
private readonly IProfileService _profileService; private readonly IQualityProfileService _qualityProfileService;
public QualityProfileSchemaController(IProfileService profileService) public QualityProfileSchemaController(IQualityProfileService qualityProfileService)
{ {
_profileService = profileService; _qualityProfileService = qualityProfileService;
} }
[HttpGet] [HttpGet]
public QualityProfileResource GetSchema() public QualityProfileResource GetSchema()
{ {
var qualityProfile = _profileService.GetDefaultProfile(string.Empty); var qualityProfile = _qualityProfileService.GetDefaultProfile(string.Empty);
return qualityProfile.ToResource(); return qualityProfile.ToResource();
} }

View File

@@ -60,10 +60,12 @@ namespace Readarr.Api.V1.RootFolders
SharedValidator.RuleFor(c => c.Name) SharedValidator.RuleFor(c => c.Name)
.NotEmpty(); .NotEmpty();
SharedValidator.RuleFor(c => c.DefaultMetadataProfileId) SharedValidator.RuleFor(c => c.DefaultMetadataProfileId).Cascade(CascadeMode.Stop)
.ValidId()
.SetValidator(metadataProfileExistsValidator); .SetValidator(metadataProfileExistsValidator);
SharedValidator.RuleFor(c => c.DefaultQualityProfileId) SharedValidator.RuleFor(c => c.DefaultQualityProfileId).Cascade(CascadeMode.Stop)
.ValidId()
.SetValidator(qualityProfileExistsValidator); .SetValidator(qualityProfileExistsValidator);
SharedValidator.RuleFor(c => c.Host).ValidHost().When(x => x.IsCalibreLibrary); SharedValidator.RuleFor(c => c.Host).ValidHost().When(x => x.IsCalibreLibrary);

View File

@@ -108,7 +108,7 @@ namespace Readarr.Api.V1.RootFolders
DefaultQualityProfileId = resource.DefaultQualityProfileId, DefaultQualityProfileId = resource.DefaultQualityProfileId,
DefaultMonitorOption = resource.DefaultMonitorOption, DefaultMonitorOption = resource.DefaultMonitorOption,
DefaultNewItemMonitorOption = resource.DefaultNewItemMonitorOption, DefaultNewItemMonitorOption = resource.DefaultNewItemMonitorOption,
DefaultTags = resource.DefaultTags, DefaultTags = resource.DefaultTags ?? new HashSet<int>(),
IsCalibreLibrary = resource.IsCalibreLibrary, IsCalibreLibrary = resource.IsCalibreLibrary,
CalibreSettings = cs CalibreSettings = cs
}; };

View File

@@ -7268,9 +7268,9 @@ write-file-atomic@^5.0.1:
signal-exit "^4.0.1" signal-exit "^4.0.1"
ws@^7.4.5: ws@^7.4.5:
version "7.5.9" version "7.5.10"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
xxhashjs@~0.2.2: xxhashjs@~0.2.2:
version "0.2.2" version "0.2.2"