diff --git a/NzbDrone.Core/Datastore/Migrations/Migration20120420.cs b/NzbDrone.Core/Datastore/Migrations/Migration20120420.cs new file mode 100644 index 000000000..62eae990f --- /dev/null +++ b/NzbDrone.Core/Datastore/Migrations/Migration20120420.cs @@ -0,0 +1,34 @@ +using System.Data; +using Migrator.Framework; + +namespace NzbDrone.Core.Datastore.Migrations +{ + [Migration(20120420)] + public class Migration20120420 : NzbDroneMigration + { + protected override void MainDbUpgrade() + { + Database.AddTable("SearchResults", new[] + { + new Column("Id", DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity), + new Column("SeriesId", DbType.Int32, ColumnProperty.NotNull), + new Column("SeasonNumber", DbType.Int32, ColumnProperty.Null), + new Column("EpisodeId", DbType.Int32, ColumnProperty.Null), + new Column("SearchTime", DbType.DateTime, ColumnProperty.NotNull), + new Column("SuccessfulDownload", DbType.Boolean, ColumnProperty.NotNull) + }); + + Database.AddTable("SearchResultItems", new[] + { + new Column("Id", DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity), + new Column("SearchResultId", DbType.Int32, ColumnProperty.NotNull), + new Column("ReportTitle", DbType.String, ColumnProperty.NotNull), + new Column("Indexer", DbType.String, ColumnProperty.NotNull), + new Column("NzbUrl", DbType.String, ColumnProperty.NotNull), + new Column("NzbInfoUrl", DbType.String, ColumnProperty.Null), + new Column("Success", DbType.Boolean, ColumnProperty.NotNull), + new Column("SearchError", DbType.Int32, ColumnProperty.NotNull) + }); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/ReportRejectionType.cs b/NzbDrone.Core/Model/ReportRejectionType.cs index 0d2ea1b46..867f37565 100644 --- a/NzbDrone.Core/Model/ReportRejectionType.cs +++ b/NzbDrone.Core/Model/ReportRejectionType.cs @@ -8,14 +8,14 @@ namespace NzbDrone.Core.Model WrongSeries = 1, QualityNotWanted = 2, WrongSeason = 3, - WrongEpisode = 3, - Size = 3, - Retention = 3, - ExistingQualityIsEqualOrBetter = 4, - Cutoff = 5, - AlreadyInQueue = 6, - DownloadClientFailure = 7, - Skipped = 8, - Failure, + WrongEpisode = 4, + Size = 5, + Retention = 6, + ExistingQualityIsEqualOrBetter = 7, + Cutoff = 8, + AlreadyInQueue = 9, + DownloadClientFailure = 10, + Skipped = 11, + Failure = 12, } } diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index a26ab3fca..4a65fae54 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -222,6 +222,7 @@ + diff --git a/NzbDrone.Core/Providers/Indexer/IndexerBase.cs b/NzbDrone.Core/Providers/Indexer/IndexerBase.cs index efda27500..d105e7a3e 100644 --- a/NzbDrone.Core/Providers/Indexer/IndexerBase.cs +++ b/NzbDrone.Core/Providers/Indexer/IndexerBase.cs @@ -192,7 +192,6 @@ namespace NzbDrone.Core.Providers.Indexer { parsedEpisode.NzbUrl = NzbDownloadUrl(item); parsedEpisode.Indexer = Name; - parsedEpisode.OriginalString = item.Title.Text; result.Add(parsedEpisode); } } @@ -237,6 +236,7 @@ namespace NzbDrone.Core.Providers.Indexer var title = TitlePreParser(item); var episodeParseResult = Parser.ParseTitle(title); + episodeParseResult.OriginalString = title; if (episodeParseResult != null) episodeParseResult.Age = DateTime.Now.Date.Subtract(item.PublishDate.Date).Days; _logger.Trace("Parsed: {0} from: {1}", episodeParseResult, item.Title.Text); diff --git a/NzbDrone.Core/Providers/SearchProvider.cs b/NzbDrone.Core/Providers/SearchProvider.cs index 324b3a2fe..fd51c6c1d 100644 --- a/NzbDrone.Core/Providers/SearchProvider.cs +++ b/NzbDrone.Core/Providers/SearchProvider.cs @@ -172,22 +172,20 @@ namespace NzbDrone.Core.Providers if (episode.Series.IsDaily) { - searchResult.AirDate = episode.AirDate.Value; searchResult.SearchResultItems = ProcessSearchResults(notification, reports, episode.Series, episode.AirDate.Value); - _searchResultProvider.Add(searchResult); if (searchResult.SearchResultItems.Any(r => r.Success)) return true; - - return false; } - if (!episode.Series.IsDaily) + else { - searchResult.SeasonNumber = episode.SeasonNumber; searchResult.EpisodeId = episodeId; - ProcessSearchResults(notification, reports, episode.Series, episode.SeasonNumber, episode.EpisodeNumber); + searchResult.SearchResultItems = ProcessSearchResults(notification, reports, episode.Series, episode.SeasonNumber, episode.EpisodeNumber); + _searchResultProvider.Add(searchResult); + + if (searchResult.SearchResultItems.Any(r => r.Success)) return true; } @@ -272,7 +270,8 @@ namespace NzbDrone.Core.Providers var item = new SearchResultItem { ReportTitle = episodeParseResult.OriginalString, - NzbUrl = episodeParseResult.NzbUrl + NzbUrl = episodeParseResult.NzbUrl, + Indexer = episodeParseResult.Indexer }; items.Add(item); @@ -312,8 +311,8 @@ namespace NzbDrone.Core.Providers continue; } - var rejectionType = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult); - if (rejectionType == ReportRejectionType.None) + item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult); + if (item.SearchError == ReportRejectionType.None) { Logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult); try @@ -360,7 +359,8 @@ namespace NzbDrone.Core.Providers var item = new SearchResultItem { ReportTitle = episodeParseResult.OriginalString, - NzbUrl = episodeParseResult.NzbUrl + NzbUrl = episodeParseResult.NzbUrl, + Indexer = episodeParseResult.Indexer }; items.Add(item); @@ -390,8 +390,8 @@ namespace NzbDrone.Core.Providers continue; } - var allowedDownload = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult); - if (allowedDownload == ReportRejectionType.None) + item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult); + if (item.SearchError == ReportRejectionType.None) { Logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult); try @@ -416,10 +416,6 @@ namespace NzbDrone.Core.Providers notification.CurrentMessage = String.Format("Unable to add report to download queue. {0}", episodeParseResult); } } - else - { - item.SearchError = allowedDownload; - } } catch (Exception e) { diff --git a/NzbDrone.Core/Providers/SearchResultProvider.cs b/NzbDrone.Core/Providers/SearchResultProvider.cs index a4b8472cb..cba3dc697 100644 --- a/NzbDrone.Core/Providers/SearchResultProvider.cs +++ b/NzbDrone.Core/Providers/SearchResultProvider.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using NLog; using Ninject; +using NzbDrone.Core.Repository; using NzbDrone.Core.Repository.Search; using PetaPoco; @@ -28,9 +29,10 @@ namespace NzbDrone.Core.Providers public virtual void Add(SearchResult searchResult) { logger.Trace("Adding new search result"); + searchResult.SuccessfulDownload = searchResult.SearchResultItems.Any(s => s.Success); var id = Convert.ToInt32(_database.Insert(searchResult)); - searchResult.SearchResultItems.ForEach(s => s.Id = id); + searchResult.SearchResultItems.ForEach(s => s.SearchResultId = id); logger.Trace("Adding search result items"); _database.InsertMany(searchResult.SearchResultItems); } @@ -46,7 +48,27 @@ namespace NzbDrone.Core.Providers public virtual List AllSearchResults() { - return _database.Fetch(); + var sql = @"SELECT SearchResults.Id, SearchResults.SeriesId, SearchResults.SeasonNumber, + SearchResults.EpisodeId, SearchResults.SearchTime, + Series.Title as SeriesTitle, Series.IsDaily, + Episodes.EpisodeNumber, Episodes.SeasonNumber, Episodes.Title as EpisodeTitle, + Episodes.AirDate, + Count(SearchResultItems.Id) as TotalItems, + SUM(CASE WHEN SearchResultItems.Success = 1 THEN 1 ELSE 0 END) as Successes + FROM SearchResults + INNER JOIN Series + ON Series.SeriesId = SearchResults.SeriesId + LEFT JOIN Episodes + ON Episodes.EpisodeId = SearchResults.EpisodeId + INNER JOIN SearchResultItems + ON SearchResultItems.SearchResultId = SearchResults.Id + GROUP BY SearchResults.Id, SearchResults.SeriesId, SearchResults.SeasonNumber, + SearchResults.EpisodeId, SearchResults.SearchTime, + Series.Title, Series.IsDaily, + Episodes.EpisodeNumber, Episodes.SeasonNumber, Episodes.Title, + Episodes.AirDate"; + + return _database.Fetch(sql); } public virtual SearchResult GetSearchResult(int id) diff --git a/NzbDrone.Core/Repository/Search/SearchResult.cs b/NzbDrone.Core/Repository/Search/SearchResult.cs index 3350895a1..50ef04550 100644 --- a/NzbDrone.Core/Repository/Search/SearchResult.cs +++ b/NzbDrone.Core/Repository/Search/SearchResult.cs @@ -15,10 +15,31 @@ namespace NzbDrone.Core.Repository.Search public int SeriesId { get; set; } public int? SeasonNumber { get; set; } public int? EpisodeId { get; set; } - public DateTime? AirDate { get; set; } public DateTime SearchTime { get; set; } + public bool SuccessfulDownload { get; set; } [ResultColumn] public List SearchResultItems { get; set; } + + [ResultColumn] + public string SeriesTitle { get; set; } + + [ResultColumn] + public bool IsDaily { get; set; } + + [ResultColumn] + public int? EpisodeNumber { get; set; } + + [ResultColumn] + public string EpisodeTitle { get; set; } + + [ResultColumn] + public DateTime AirDate { get; set; } + + [ResultColumn] + public int TotalItems { get; set; } + + [ResultColumn] + public int Successes { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Repository/Search/SearchResultItem.cs b/NzbDrone.Core/Repository/Search/SearchResultItem.cs index 94160a6b1..51b744e59 100644 --- a/NzbDrone.Core/Repository/Search/SearchResultItem.cs +++ b/NzbDrone.Core/Repository/Search/SearchResultItem.cs @@ -14,6 +14,7 @@ namespace NzbDrone.Core.Repository.Search public int Id { get; set; } public int SearchResultId { get; set; } public string ReportTitle { get; set; } + public string Indexer { get; set; } public string NzbUrl { get; set; } public string NzbInfoUrl { get; set; } public bool Success { get; set; } diff --git a/NzbDrone.Web/Controllers/LogController.cs b/NzbDrone.Web/Controllers/LogController.cs index 60f9668de..5db86794c 100644 --- a/NzbDrone.Web/Controllers/LogController.cs +++ b/NzbDrone.Web/Controllers/LogController.cs @@ -7,6 +7,8 @@ using System.Web.Mvc; using DataTables.Mvc.Core.Models; using NzbDrone.Common; using NzbDrone.Core.Instrumentation; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Repository.Search; using NzbDrone.Web.Models; namespace NzbDrone.Web.Controllers @@ -16,12 +18,15 @@ namespace NzbDrone.Web.Controllers private readonly LogProvider _logProvider; private readonly EnvironmentProvider _environmentProvider; private readonly DiskProvider _diskProvider; + private readonly SearchResultProvider _searchResultProvider; - public LogController(LogProvider logProvider, EnvironmentProvider environmentProvider, DiskProvider diskProvider) + public LogController(LogProvider logProvider, EnvironmentProvider environmentProvider, + DiskProvider diskProvider, SearchResultProvider searchResultProvider) { _logProvider = logProvider; _environmentProvider = environmentProvider; _diskProvider = diskProvider; + _searchResultProvider = searchResultProvider; } public ActionResult Index() @@ -50,6 +55,28 @@ namespace NzbDrone.Web.Controllers return JsonNotificationResult.Info("Logs Cleared"); } + public ActionResult SearchResults() + { + var results = _searchResultProvider.AllSearchResults(); + + var model = results.Select(s => new SearchResultsModel + { + Id = s.Id, + SearchTime = s.SearchTime.ToString(), + DisplayName = GetDisplayName(s), + ReportCount = s.TotalItems, + Successful = s.Successes > 0 + }); + + return View(model); + } + + public ActionResult SearchDetails(int searchId) + { + var model = _searchResultProvider.GetSearchResult(searchId); + return View(model); + } + public ActionResult AjaxBinding(DataTablesParams dataTablesParams) { var logs = _logProvider.GetAllLogs(); @@ -102,5 +129,24 @@ namespace NzbDrone.Web.Controllers }, JsonRequestBehavior.AllowGet); } + + public string GetDisplayName(SearchResult searchResult) + { + if (!searchResult.EpisodeNumber.HasValue) + { + return String.Format("{0} - Season {1}", searchResult.SeriesTitle, searchResult.SeasonNumber); + } + + string episodeString; + + if (searchResult.IsDaily) + episodeString = searchResult.AirDate.ToShortDateString().Replace('/', '-'); + + else + episodeString = String.Format("S{0:00}E{1:00}", searchResult.SeasonNumber, + searchResult.EpisodeNumber); + + return String.Format("{0} - {1} - {2}", searchResult.SeriesTitle, episodeString, searchResult.EpisodeTitle); + } } } \ No newline at end of file diff --git a/NzbDrone.Web/Models/SearchResultsModel.cs b/NzbDrone.Web/Models/SearchResultsModel.cs new file mode 100644 index 000000000..178844c1f --- /dev/null +++ b/NzbDrone.Web/Models/SearchResultsModel.cs @@ -0,0 +1,13 @@ +using System; + +namespace NzbDrone.Web.Models +{ + public class SearchResultsModel + { + public int Id { get; set; } + public string DisplayName { get; set; } + public string SearchTime { get; set; } + public int ReportCount { get; set; } + public bool Successful { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index 1d77ee308..778b6edcf 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -237,6 +237,7 @@ + @@ -519,6 +520,9 @@ + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/NzbDrone.Web/Views/Log/SearchResults.cshtml b/NzbDrone.Web/Views/Log/SearchResults.cshtml new file mode 100644 index 000000000..24ac7cadb --- /dev/null +++ b/NzbDrone.Web/Views/Log/SearchResults.cshtml @@ -0,0 +1,22 @@ +@using DataTables.Mvc.Core +@model IEnumerable + +@{ + ViewBag.Title = "Search Results"; +} + +@Html.GridHtml("searchResultsGrid", "dataTablesGrid") + +@section Scripts +{ + @( + Html.GridScriptForModel("#searchResultsGrid") + .PageLength(20) + .ChangePageLength(false) + .AddColumn(new Column().DataProperty("DisplayName").Link("SearchDetails?searchId={Id}", "{DisplayName}").Title("Name")) + .AddColumn(new Column().DataProperty("SearchTime").Title("Time").Width("170px")) + .AddColumn(new Column().DataProperty("ReportCount").Title("Reports Found").Width("140px")) + .AddColumn(new Column().DataProperty("Successful").Title("Successful").Width("110px")) + .AddSorting(1) + ) +}