diff --git a/frontend/build/webpack.config.js b/frontend/build/webpack.config.js index 733e2bc4d..e0ec27c27 100644 --- a/frontend/build/webpack.config.js +++ b/frontend/build/webpack.config.js @@ -91,7 +91,8 @@ module.exports = (env) => { }), new MiniCssExtractPlugin({ - filename: 'Content/styles.css' + filename: 'Content/styles.css', + chunkFilename: 'Content/[id]-[chunkhash].css' }), new HtmlWebpackPlugin({ diff --git a/frontend/src/Activity/Queue/QueueRow.js b/frontend/src/Activity/Queue/QueueRow.js index 708f960bb..0f66fbfa4 100644 --- a/frontend/src/Activity/Queue/QueueRow.js +++ b/frontend/src/Activity/Queue/QueueRow.js @@ -45,14 +45,14 @@ class QueueRow extends Component { this.setState({ isRemoveQueueItemModalOpen: true }); }; - onRemoveQueueItemModalConfirmed = (blocklist) => { + onRemoveQueueItemModalConfirmed = (blocklist, skipRedownload) => { const { onRemoveQueueItemPress, onQueueRowModalOpenOrClose } = this.props; onQueueRowModalOpenOrClose(false); - onRemoveQueueItemPress(blocklist); + onRemoveQueueItemPress(blocklist, skipRedownload); this.setState({ isRemoveQueueItemModalOpen: false }); }; diff --git a/frontend/src/Activity/Queue/RemoveQueueItemModal.js b/frontend/src/Activity/Queue/RemoveQueueItemModal.js index f1a152734..8260d4362 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemModal.js +++ b/frontend/src/Activity/Queue/RemoveQueueItemModal.js @@ -10,6 +10,7 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { inputTypes, kinds, sizes } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; class RemoveQueueItemModal extends Component { @@ -21,7 +22,8 @@ class RemoveQueueItemModal extends Component { this.state = { remove: true, - blocklist: false + blocklist: false, + skipRedownload: false }; } @@ -31,7 +33,8 @@ class RemoveQueueItemModal extends Component { resetState = function() { this.setState({ remove: true, - blocklist: false + blocklist: false, + skipRedownload: false }); }; @@ -46,6 +49,10 @@ class RemoveQueueItemModal extends Component { this.setState({ blocklist: value }); }; + onSkipRedownloadChange = ({ value }) => { + this.setState({ skipRedownload: value }); + }; + onRemoveConfirmed = () => { const state = this.state; @@ -69,7 +76,7 @@ class RemoveQueueItemModal extends Component { isPending } = this.props; - const { remove, blocklist } = this.state; + const { remove, blocklist, skipRedownload } = this.state; return ( + { + blocklist ? + + {translate('SkipRedownload')} + + : + null + } diff --git a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js index 738559ed5..9ee6fef87 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js +++ b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js @@ -23,7 +23,8 @@ class RemoveQueueItemsModal extends Component { this.state = { remove: true, - blocklist: false + blocklist: false, + skipRedownload: false }; } @@ -33,7 +34,8 @@ class RemoveQueueItemsModal extends Component { resetState = function() { this.setState({ remove: true, - blocklist: false + blocklist: false, + skipRedownload: false }); }; @@ -48,6 +50,10 @@ class RemoveQueueItemsModal extends Component { this.setState({ blocklist: value }); }; + onSkipRedownloadChange = ({ value }) => { + this.setState({ skipRedownload: value }); + }; + onRemoveConfirmed = () => { const state = this.state; @@ -71,7 +77,7 @@ class RemoveQueueItemsModal extends Component { allPending } = this.props; - const { remove, blocklist } = this.state; + const { remove, blocklist, skipRedownload } = this.state; return ( + { + blocklist ? + + {translate('SkipRedownload')} + + : + null + } diff --git a/frontend/src/Series/Index/Table/SeriesIndexTable.css b/frontend/src/Series/Index/Table/SeriesIndexTable.css index 455f0bc7c..0bfc5fec4 100644 --- a/frontend/src/Series/Index/Table/SeriesIndexTable.css +++ b/frontend/src/Series/Index/Table/SeriesIndexTable.css @@ -1,3 +1,11 @@ .tableScroller { position: relative; } + +.row { + transition: background-color 500ms; + + &:hover { + background-color: var(--tableRowHoverBackgroundColor); + } +} diff --git a/frontend/src/Series/Index/Table/SeriesIndexTable.css.d.ts b/frontend/src/Series/Index/Table/SeriesIndexTable.css.d.ts index 712cb8f72..ff35c263f 100644 --- a/frontend/src/Series/Index/Table/SeriesIndexTable.css.d.ts +++ b/frontend/src/Series/Index/Table/SeriesIndexTable.css.d.ts @@ -1,6 +1,7 @@ // This file is automatically generated. // Please do not change this file! interface CssExports { + 'row': string; 'tableScroller': string; } export const cssExports: CssExports; diff --git a/frontend/src/Series/Index/Table/SeriesIndexTable.tsx b/frontend/src/Series/Index/Table/SeriesIndexTable.tsx index c0d5e169c..c1401f984 100644 --- a/frontend/src/Series/Index/Table/SeriesIndexTable.tsx +++ b/frontend/src/Series/Index/Table/SeriesIndexTable.tsx @@ -65,6 +65,7 @@ const Row: React.FC> = ({ justifyContent: 'space-between', ...style, }} + className={styles.row} > - #sonarr on Libera + + {translate('IRCLinkText')} + - Libera webchat + + {translate('LiberaWebchat')} + diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs index 3c7a17c8a..674522748 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleFixture.cs @@ -70,6 +70,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests [TestCase("[a] title", "a title")] [TestCase("backslash \\ backlash", "backslash backlash")] [TestCase("I'm the Boss", "Im the Boss")] + [TestCase("The Title's", "The Title's")] + [TestCase("I'm after I'm", "Im after I'm")] // [TestCase("", "")] public void should_get_expected_title_back(string title, string expected) diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleWithoutYearFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleWithoutYearFixture.cs index 1cf761402..8e3a3f0b7 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleWithoutYearFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleWithoutYearFixture.cs @@ -54,6 +54,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests [TestCase("The Mist", 2018, "The Mist")] [TestCase("The Rat Pack (A&E)", 1999, "The Rat Pack AandE")] [TestCase("The Climax: I (Almost) Got Away With It (2016)", 2016, "The Climax I Almost Got Away With It")] + [TestCase("The Series Title's (2016)", 2016, "The Series Titles")] + [TestCase("The Series Title's", 2016, "The Series Title's")] public void should_get_expected_title_back(string title, int year, string expected) { _series.Title = title; diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleYearFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleYearFixture.cs index 8ed607983..92fce8e8d 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleYearFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/CleanTitleYearFixture.cs @@ -54,6 +54,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests [TestCase("The Mist", 2018, "The Mist 2018")] [TestCase("The Rat Pack (A&E)", 1999, "The Rat Pack AandE 1999")] [TestCase("The Climax: I (Almost) Got Away With It (2016)", 2016, "The Climax I Almost Got Away With It 2016")] + [TestCase("The Series Title's", 2016, "The Series Titles 2016")] public void should_get_expected_title_back(string title, int year, string expected) { _series.Title = title; diff --git a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs index 143eba14e..757e5da97 100644 --- a/src/NzbDrone.Core/Download/DownloadFailedEvent.cs +++ b/src/NzbDrone.Core/Download/DownloadFailedEvent.cs @@ -23,5 +23,6 @@ namespace NzbDrone.Core.Download public Dictionary Data { get; set; } public TrackedDownload TrackedDownload { get; set; } public List Languages { get; set; } + public bool SkipRedownload { get; set; } } } diff --git a/src/NzbDrone.Core/Download/FailedDownloadService.cs b/src/NzbDrone.Core/Download/FailedDownloadService.cs index f26741ca8..8e07b23fd 100644 --- a/src/NzbDrone.Core/Download/FailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/FailedDownloadService.cs @@ -9,8 +9,8 @@ namespace NzbDrone.Core.Download { public interface IFailedDownloadService { - void MarkAsFailed(int historyId); - void MarkAsFailed(string downloadId); + void MarkAsFailed(int historyId, bool skipRedownload = false); + void MarkAsFailed(string downloadId, bool skipRedownload = false); void Check(TrackedDownload trackedDownload); void ProcessFailed(TrackedDownload trackedDownload); } @@ -30,14 +30,14 @@ namespace NzbDrone.Core.Download _eventAggregator = eventAggregator; } - public void MarkAsFailed(int historyId) + public void MarkAsFailed(int historyId, bool skipRedownload = false) { var history = _historyService.Get(historyId); var downloadId = history.DownloadId; if (downloadId.IsNullOrWhiteSpace()) { - PublishDownloadFailedEvent(new List { history }, "Manually marked as failed"); + PublishDownloadFailedEvent(new List { history }, "Manually marked as failed", skipRedownload: skipRedownload); return; } @@ -57,7 +57,7 @@ namespace NzbDrone.Core.Download PublishDownloadFailedEvent(grabbedHistory, "Manually marked as failed"); } - public void MarkAsFailed(string downloadId) + public void MarkAsFailed(string downloadId, bool skipRedownload = false) { var history = _historyService.Find(downloadId, EpisodeHistoryEventType.Grabbed); @@ -65,7 +65,7 @@ namespace NzbDrone.Core.Download { var trackedDownload = _trackedDownloadService.Find(downloadId); - PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload); + PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload); } } @@ -125,7 +125,7 @@ namespace NzbDrone.Core.Download PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload); } - private void PublishDownloadFailedEvent(List historyItems, string message, TrackedDownload trackedDownload = null) + private void PublishDownloadFailedEvent(List historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false) { var historyItem = historyItems.First(); @@ -140,7 +140,8 @@ namespace NzbDrone.Core.Download Message = message, Data = historyItem.Data, TrackedDownload = trackedDownload, - Languages = historyItem.Languages + Languages = historyItem.Languages, + SkipRedownload = skipRedownload }; _eventAggregator.PublishEvent(downloadFailedEvent); diff --git a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs index ed23f021b..20c19e15d 100644 --- a/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs +++ b/src/NzbDrone.Core/Download/RedownloadFailedDownloadService.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using NLog; using NzbDrone.Core.Configuration; using NzbDrone.Core.IndexerSearch; @@ -30,6 +30,12 @@ namespace NzbDrone.Core.Download [EventHandleOrder(EventHandleOrder.Last)] public void Handle(DownloadFailedEvent message) { + if (message.SkipRedownload) + { + _logger.Debug("Skip redownloading requested by user"); + return; + } + if (!_configService.AutoRedownloadFailed) { _logger.Debug("Auto redownloading failed episodes is disabled"); diff --git a/src/NzbDrone.Core/ImportLists/Simkl/SimklAPI.cs b/src/NzbDrone.Core/ImportLists/Simkl/SimklAPI.cs index 4472af249..0290dabfb 100644 --- a/src/NzbDrone.Core/ImportLists/Simkl/SimklAPI.cs +++ b/src/NzbDrone.Core/ImportLists/Simkl/SimklAPI.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Core.ImportLists.Simkl public string Imdb { get; set; } public string Tmdb { get; set; } public string Tvdb { get; set; } + public string Mal { get; set; } } public class SimklSeriesPropsResource @@ -23,11 +24,15 @@ namespace NzbDrone.Core.ImportLists.Simkl public class SimklSeriesResource { public SimklSeriesPropsResource Show { get; set; } + + [JsonProperty("anime_type")] + public SimklAnimeType AnimeType { get; set; } } public class SimklResponse { public List Shows { get; set; } + public List Anime { get; set; } } public class RefreshRequestResponse @@ -66,4 +71,10 @@ namespace NzbDrone.Core.ImportLists.Simkl { public DateTime All { get; set; } } + + public enum SimklAnimeType + { + Tv, + Movie + } } diff --git a/src/NzbDrone.Core/ImportLists/Simkl/SimklParser.cs b/src/NzbDrone.Core/ImportLists/Simkl/SimklParser.cs index 6f5758369..1419c6be7 100644 --- a/src/NzbDrone.Core/ImportLists/Simkl/SimklParser.cs +++ b/src/NzbDrone.Core/ImportLists/Simkl/SimklParser.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Net; +using NLog; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Serializer; using NzbDrone.Core.ImportLists.Exceptions; using NzbDrone.Core.Parser.Model; @@ -10,6 +12,11 @@ namespace NzbDrone.Core.ImportLists.Simkl public class SimklParser : IParseImportListResponse { private ImportListResponse _importResponse; + private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(SimklParser)); + + public SimklParser() + { + } public virtual IList ParseResponse(ImportListResponse importResponse) { @@ -22,7 +29,7 @@ namespace NzbDrone.Core.ImportLists.Simkl return series; } - var jsonResponse = STJson.Deserialize(_importResponse.Content); + var jsonResponse = Json.Deserialize(_importResponse.Content); // no shows were return if (jsonResponse == null) @@ -30,14 +37,40 @@ namespace NzbDrone.Core.ImportLists.Simkl return series; } - foreach (var show in jsonResponse.Shows) + if (jsonResponse.Anime != null) { - series.AddIfNotNull(new ImportListItemInfo() + foreach (var show in jsonResponse.Anime) { - Title = show.Show.Title, - TvdbId = int.TryParse(show.Show.Ids.Tvdb, out var tvdbId) ? tvdbId : 0, - ImdbId = show.Show.Ids.Imdb - }); + var tentativeTvdbId = int.TryParse(show.Show.Ids.Tvdb, out var tvdbId) ? tvdbId : 0; + + if (tentativeTvdbId > 0 && show.AnimeType == SimklAnimeType.Tv) + { + series.AddIfNotNull(new ImportListItemInfo() + { + Title = show.Show.Title, + ImdbId = show.Show.Ids.Imdb, + TvdbId = tvdbId, + MalId = int.TryParse(show.Show.Ids.Mal, out var malId) ? malId : 0 + }); + } + else + { + Logger.Warn("Skipping info grabbing for '{0}' because it is a movie or it is not the first season of the show", show.Show.Title); + } + } + } + + if (jsonResponse.Shows != null) + { + foreach (var show in jsonResponse.Shows) + { + series.AddIfNotNull(new ImportListItemInfo() + { + Title = show.Show.Title, + TvdbId = int.TryParse(show.Show.Ids.Tvdb, out var tvdbId) ? tvdbId : 0, + ImdbId = show.Show.Ids.Imdb + }); + } } return series; diff --git a/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserRequestGenerator.cs b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserRequestGenerator.cs index 90bdccf1a..74ea05831 100644 --- a/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserRequestGenerator.cs +++ b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserRequestGenerator.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.ImportLists.Simkl.User private IEnumerable GetSeriesRequest() { - var link = $"{Settings.BaseUrl.Trim()}/sync/all-items/shows/{((SimklUserListType)Settings.ListType).ToString().ToLowerInvariant()}"; + var link = $"{Settings.BaseUrl.Trim()}/sync/all-items/{((SimklUserShowType)Settings.ShowType).ToString().ToLowerInvariant()}/{((SimklUserListType)Settings.ListType).ToString().ToLowerInvariant()}"; var request = new ImportListRequest(link, HttpAccept.Json); diff --git a/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserSettings.cs b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserSettings.cs index 61cc48129..65f19aa3f 100644 --- a/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserSettings.cs +++ b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserSettings.cs @@ -19,9 +19,13 @@ namespace NzbDrone.Core.ImportLists.Simkl.User public SimklUserSettings() { ListType = (int)SimklUserListType.Watching; + ShowType = (int)SimklUserShowType.Shows; } [FieldDefinition(1, Label = "List Type", Type = FieldType.Select, SelectOptions = typeof(SimklUserListType), HelpText = "Type of list you're seeking to import from")] public int ListType { get; set; } + + [FieldDefinition(1, Label = "Show Type", Type = FieldType.Select, SelectOptions = typeof(SimklUserShowType), HelpText = "Type of show you're seeking to import from")] + public int ShowType { get; set; } } } diff --git a/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserShowType.cs b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserShowType.cs new file mode 100644 index 000000000..8ff2eb16d --- /dev/null +++ b/src/NzbDrone.Core/ImportLists/Simkl/User/SimklUserShowType.cs @@ -0,0 +1,8 @@ +namespace NzbDrone.Core.ImportLists.Simkl.User +{ + public enum SimklUserShowType + { + Shows = 0, + Anime = 1 + } +} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 31d7cfe44..5a01aea69 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -139,12 +139,14 @@ "InstallLatest": "Install Latest", "Interval": "Interval", "IRC": "IRC", + "IRCLinkText": "#sonarr on Libera", "Language": "Language", "Language that Sonarr will use for UI": "Language that Sonarr will use for UI", "Languages": "Languages", "LastDuration": "Last Duration", "LastExecution": "Last Execution", "LastWriteTime": "Last Write Time", + "LiberaWebchat": "Libera Webchat", "LibraryImport": "Library Import", "Location": "Location", "LogFiles": "Log Files", @@ -189,7 +191,7 @@ "NoSeasons": "No seasons", "NoUpdatesAreAvailable": "No updates are available", "OneSeason": "1 season", - "OnLatestVersion": "The latest version of Radarr is already installed", + "OnLatestVersion": "The latest version of Sonarr is already installed", "Options": "Options", "OriginalLanguage": "Original Language", "PackageVersion": "Package Version", @@ -276,6 +278,8 @@ "ShownClickToHide": "Shown, click to hide", "Size": "Size", "SizeOnDisk": "Size on disk", + "SkipRedownloadHelpText": "Prevents Sonarr from trying to download an alternative release for this item", + "SkipRedownload": "Skip Redownload", "Source": "Source", "Special": "Special", "Started": "Started", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 881648a48..fa9e847d3 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -214,5 +214,100 @@ "SeasonNumber": "Número da Temporada", "SeriesTitle": "Título da Série", "Special": "Especial", - "TestParsing": "Análise de Teste" + "TestParsing": "Análise de Teste", + "About": "Sobre", + "Actions": "Ações", + "AppDataDirectory": "Diretório AppData", + "AptUpdater": "Usar apt para instalar atualizações", + "BackupNow": "Fazer Backup Agora", + "Backups": "Backups", + "BeforeUpdate": "Antes de atualizar", + "CancelPendingTask": "Tem certeza de que deseja cancelar esta tarefa pendente?", + "Clear": "Limpar", + "CurrentlyInstalled": "Atualmente instalado", + "DeleteBackup": "Excluir Backup", + "DeleteBackupMessageText": "Tem certeza de que deseja excluir o backup '{name}'?", + "Discord": "Discord", + "DiskSpace": "Espaço em Disco", + "Docker": "Docker", + "DockerUpdater": "Atualize o contêiner docker para receber a atualização", + "Donations": "Doações", + "DotNetVersion": ".NET", + "Download": "Baixar", + "Duration": "Duração", + "ErrorRestoringBackup": "Erro ao restaurar o backup", + "Exception": "Exceção", + "ExternalUpdater": "O Sonarr está configurado para usar um mecanismo de atualização externo", + "FailedToFetchUpdates": "Falha ao buscar atualizações", + "FailedToUpdateSettings": "Falha ao atualizar as configurações", + "FeatureRequests": "Solicitações de recursos", + "Filename": "Nome do arquivo", + "Fixed": "Corrigido", + "Forums": "Fóruns", + "FreeSpace": "Espaço Livre", + "From": "De", + "GeneralSettings": "Configurações Gerais", + "Health": "Saúde", + "HomePage": "Página Inicial", + "OnLatestVersion": "A versão mais recente do Sonarr já está instalada", + "InstallLatest": "Instalar o mais recente", + "Interval": "Intervalo", + "IRC": "IRC", + "LastDuration": "Última Duração", + "LastExecution": "Última Execução", + "LastWriteTime": "Hora da Última Gravação", + "Location": "Localização", + "LogFilesLocation": "Os arquivos de log estão localizados em: {location}", + "Logs": "Logs", + "MaintenanceRelease": "Versão de manutenção: correções de bugs e outras melhorias. Veja Github Commit History para mais detalhes", + "Manual": "Manual", + "Message": "Mensagem", + "Mode": "Modo", + "MoreInfo": "Mais informações", + "New": "Novo", + "NextExecution": "Próxima Execução", + "NoBackupsAreAvailable": "Não há backups disponíveis", + "NoEventsFound": "Nenhum evento encontrado", + "NoIssuesWithYourConfiguration": "Sem problemas com sua configuração", + "NoLeaveIt": "Não, deixe-o", + "NoLogFiles": "Nenhum arquivo de log", + "NoUpdatesAreAvailable": "Nenhuma atualização está disponível", + "Options": "Opções", + "PackageVersion": "Versão do pacote", + "PackageVersionInfo": "{packageVersion} por {packageAuthor}", + "PreviouslyInstalled": "Instalado anteriormente", + "Queued": "Enfileirados", + "ReadTheWikiForMoreInformation": "Leia o Wiki para mais informações", + "Refresh": "Atualizar", + "Reload": "Recarregar", + "RemovedFromTaskQueue": "Removido da fila de tarefas", + "Restart": "Reiniciar", + "TaskUserAgentTooltip": "User-Agent fornecido pelo aplicativo que chamou a API", + "ResetQualityDefinitions": "Redefinir Definições de Qualidade", + "ResetQualityDefinitionsMessageText": "Tem certeza de que deseja redefinir as definições de qualidade?", + "ResetTitles": "Redefinir Títulos", + "Restore": "Restaurar", + "RestoreBackup": "Restaurar backup", + "Scheduled": "Agendado", + "SeriesEditor": "Editor de séries", + "Size": "Tamanho", + "Source": "Fonte", + "Started": "Iniciado", + "StartupDirectory": "Diretório de inicialização", + "Status": "Estado", + "TestAll": "Testar Tudo", + "TheLogLevelDefault": "O padrão do nível de log é 'Info' e pode ser alterado em [Configurações gerais](/configurações /geral)", + "Time": "Horário", + "TotalSpace": "Espaço Total", + "Twitter": "Twitter", + "UnableToLoadBackups": "Não foi possível carregar os backups", + "UnableToUpdateSonarrDirectly": "Incapaz de atualizar o Sonarr diretamente,", + "UpdaterLogFiles": "Arquivos de log do atualizador", + "Uptime": "Tempo de atividade", + "Wiki": "Wiki", + "WouldYouLikeToRestoreBackup": "Gostaria de restaurar o backup '{name}'?", + "YesCancel": "Sim, Cancelar", + "Reset": "Redefinir", + "ResetDefinitionTitlesHelpText": "Redefinir títulos de definição, bem como valores", + "RestartReloadNote": "Observação: o Sonarr reiniciará automaticamente e recarregará a IU durante o processo de restauração." } diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs index 6e66633d3..a8d89364a 100644 --- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs @@ -44,7 +44,7 @@ namespace NzbDrone.Core.Organizer _standardSeries = new Series { SeriesType = SeriesTypes.Standard, - Title = "The Series Title!", + Title = "The Series Title's!", Year = 2010, ImdbId = "tt12345", TvdbId = 12345, @@ -54,7 +54,7 @@ namespace NzbDrone.Core.Organizer _dailySeries = new Series { SeriesType = SeriesTypes.Daily, - Title = "The Series Title!", + Title = "The Series Title's!", Year = 2010, ImdbId = "tt12345", TvdbId = 12345, @@ -64,7 +64,7 @@ namespace NzbDrone.Core.Organizer _animeSeries = new Series { SeriesType = SeriesTypes.Anime, - Title = "The Series Title!", + Title = "The Series Title's!", Year = 2010, ImdbId = "tt12345", TvdbId = 12345, @@ -141,45 +141,45 @@ namespace NzbDrone.Core.Organizer _singleEpisodeFile = new EpisodeFile { - Quality = new QualityModel(Quality.HDTV720p, new Revision(2)), - RelativePath = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE.mkv", - SceneName = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE", + Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2)), + RelativePath = "The.Series.Title's!.S01E01.1080p.WEBDL.x264-EVOLVE.mkv", + SceneName = "The.Series.Title's!.S01E01.1080p.WEBDL.x264-EVOLVE", ReleaseGroup = "RlsGrp", MediaInfo = mediaInfo }; _multiEpisodeFile = new EpisodeFile { - Quality = new QualityModel(Quality.HDTV720p, new Revision(2)), - RelativePath = "Series.Title.S01E01-E03.720p.HDTV.x264-EVOLVE.mkv", - SceneName = "Series.Title.S01E01-E03.720p.HDTV.x264-EVOLVE", + Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2)), + RelativePath = "The.Series.Title's!.S01E01-E03.1080p.WEBDL.x264-EVOLVE.mkv", + SceneName = "The.Series.Title's!.S01E01-E03.1080p.WEBDL.x264-EVOLVE", ReleaseGroup = "RlsGrp", MediaInfo = mediaInfo, }; _dailyEpisodeFile = new EpisodeFile { - Quality = new QualityModel(Quality.HDTV720p, new Revision(2)), - RelativePath = "Series.Title.2013.10.30.HDTV.x264-EVOLVE.mkv", - SceneName = "Series.Title.2013.10.30.HDTV.x264-EVOLVE", + Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2)), + RelativePath = "The.Series.Title's!.2013.10.30.1080p.WEBDL.x264-EVOLVE.mkv", + SceneName = "The.Series.Title's!.2013.10.30.1080p.WEBDL.x264-EVOLVE", ReleaseGroup = "RlsGrp", MediaInfo = mediaInfo }; _animeEpisodeFile = new EpisodeFile { - Quality = new QualityModel(Quality.HDTV720p, new Revision(2)), - RelativePath = "[RlsGroup] Series Title - 001 [720p].mkv", - SceneName = "[RlsGroup] Series Title - 001 [720p]", + Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2)), + RelativePath = "[RlsGroup] The Series Title's! - 001 [1080P].mkv", + SceneName = "[RlsGroup] The Series Title's! - 001 [1080P]", ReleaseGroup = "RlsGrp", MediaInfo = mediaInfoAnime }; _animeMultiEpisodeFile = new EpisodeFile { - Quality = new QualityModel(Quality.HDTV720p, new Revision(2)), - RelativePath = "[RlsGroup] Series Title - 001 - 103 [720p].mkv", - SceneName = "[RlsGroup] Series Title - 001 - 103 [720p]", + Quality = new QualityModel(Quality.WEBDL1080p, new Revision(2)), + RelativePath = "[RlsGroup] The Series Title's! - 001 - 103 [1080p].mkv", + SceneName = "[RlsGroup] The Series Title's! - 001 - 103 [1080p]", ReleaseGroup = "RlsGrp", MediaInfo = mediaInfoAnime }; diff --git a/src/NzbDrone.Core/Parser/Model/ImportListItemInfo.cs b/src/NzbDrone.Core/Parser/Model/ImportListItemInfo.cs index df76ac2c2..182c2fca9 100644 --- a/src/NzbDrone.Core/Parser/Model/ImportListItemInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ImportListItemInfo.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Core.Parser.Model public int TvdbId { get; set; } public int TmdbId { get; set; } public string ImdbId { get; set; } + public int MalId { get; set; } public DateTime ReleaseDate { get; set; } public override string ToString() diff --git a/src/Sonarr.Api.V3/Queue/QueueController.cs b/src/Sonarr.Api.V3/Queue/QueueController.cs index 605bc9db9..6259956c3 100644 --- a/src/Sonarr.Api.V3/Queue/QueueController.cs +++ b/src/Sonarr.Api.V3/Queue/QueueController.cs @@ -70,7 +70,7 @@ namespace Sonarr.Api.V3.Queue } [RestDeleteById] - public void RemoveAction(int id, bool removeFromClient = true, bool blocklist = false) + public void RemoveAction(int id, bool removeFromClient = true, bool blocklist = false, bool skipRedownload = false) { var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id); @@ -88,12 +88,12 @@ namespace Sonarr.Api.V3.Queue throw new NotFoundException(); } - Remove(trackedDownload, removeFromClient, blocklist); + Remove(trackedDownload, removeFromClient, blocklist, skipRedownload); _trackedDownloadService.StopTracking(trackedDownload.DownloadItem.DownloadId); } [HttpDelete("bulk")] - public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false) + public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false, [FromQuery] bool skipRedownload = false) { var trackedDownloadIds = new List(); var pendingToRemove = new List(); @@ -124,7 +124,7 @@ namespace Sonarr.Api.V3.Queue foreach (var trackedDownload in trackedToRemove.DistinctBy(t => t.DownloadItem.DownloadId)) { - Remove(trackedDownload, removeFromClient, blocklist); + Remove(trackedDownload, removeFromClient, blocklist, skipRedownload); trackedDownloadIds.Add(trackedDownload.DownloadItem.DownloadId); } @@ -255,7 +255,7 @@ namespace Sonarr.Api.V3.Queue _pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id); } - private TrackedDownload Remove(TrackedDownload trackedDownload, bool removeFromClient, bool blocklist) + private TrackedDownload Remove(TrackedDownload trackedDownload, bool removeFromClient, bool blocklist, bool skipRedownload) { if (removeFromClient) { @@ -271,7 +271,7 @@ namespace Sonarr.Api.V3.Queue if (blocklist) { - _failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId); + _failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipRedownload); } if (!removeFromClient && !blocklist) diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index f585f2877..60543a7a3 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -5132,6 +5132,14 @@ "type": "boolean", "default": false } + }, + { + "name": "skipRedownload", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } } ], "responses": { @@ -5162,6 +5170,14 @@ "type": "boolean", "default": false } + }, + { + "name": "skipRedownload", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } } ], "requestBody": { diff --git a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs index dcbcb8ed0..4f1bb8383 100644 --- a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs @@ -123,7 +123,7 @@ namespace Sonarr.Http.ClientSchema Placeholder = fieldAttribute.Placeholder }; - if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect) + if (fieldAttribute.Type is FieldType.Select or FieldType.TagSelect) { if (fieldAttribute.SelectOptionsProviderAction.IsNotNullOrWhiteSpace()) { @@ -172,31 +172,33 @@ namespace Sonarr.Http.ClientSchema { if (selectOptions.IsEnum) { - var options = selectOptions.GetFields().Where(v => v.IsStatic).Select(v => - { - var name = v.Name.Replace('_', ' '); - var value = Convert.ToInt32(v.GetRawConstantValue()); - var attrib = v.GetCustomAttribute(); - if (attrib != null) + var options = selectOptions + .GetFields() + .Where(v => v.IsStatic && !v.GetCustomAttributes(false).OfType().Any()) + .Select(v => { - return new SelectOption + var name = v.Name.Replace('_', ' '); + var value = Convert.ToInt32(v.GetRawConstantValue()); + var attrib = v.GetCustomAttribute(); + + if (attrib != null) { - Value = value, - Name = attrib.Label ?? name, - Order = attrib.Order, - Hint = attrib.Hint ?? $"({value})" - }; - } - else - { + return new SelectOption + { + Value = value, + Name = attrib.Label ?? name, + Order = attrib.Order, + Hint = attrib.Hint ?? $"({value})" + }; + } + return new SelectOption { Value = value, Name = name, Order = value }; - } - }); + }); return options.OrderBy(o => o.Order).ToList(); }