diff --git a/NzbDrone.Core.Test/IndexerProviderTest.cs b/NzbDrone.Core.Test/IndexerProviderTest.cs index ba2e693d2..da057daf3 100644 --- a/NzbDrone.Core.Test/IndexerProviderTest.cs +++ b/NzbDrone.Core.Test/IndexerProviderTest.cs @@ -30,9 +30,10 @@ namespace NzbDrone.Core.Test list.Add(new Indexer { IndexerName = "Test4", RssUrl = "http://www.test4.com/rss.php", Enabled = false, Order = 2 }); var repo = new Mock(); + var config = new Mock(); repo.Setup(r => r.All()).Returns(list.AsQueryable()); - var target = new IndexerProvider(repo.Object); + var target = new IndexerProvider(repo.Object, config.Object); //Act var result = target.AllIndexers(); @@ -57,9 +58,10 @@ namespace NzbDrone.Core.Test list.Add(new Indexer { IndexerName = "Test4", RssUrl = "http://www.test4.com/rss.php", Enabled = false, Order = 2 }); var repo = new Mock(); + var config = new Mock(); repo.Setup(r => r.All()).Returns(list.AsQueryable()); - var target = new IndexerProvider(repo.Object); + var target = new IndexerProvider(repo.Object, config.Object); //Act var result = target.EnabledIndexers(); diff --git a/NzbDrone.Core.Test/ParserTest.cs b/NzbDrone.Core.Test/ParserTest.cs index e8c3dcad1..267f65054 100644 --- a/NzbDrone.Core.Test/ParserTest.cs +++ b/NzbDrone.Core.Test/ParserTest.cs @@ -57,9 +57,9 @@ namespace NzbDrone.Core.Test [Test] [Row(@"c:\test\", @"c:\test")] [Row(@"c:\\test\\", @"c:\test")] - [Row(@"C:\\Test\\", @"c:\test")] - [Row(@"C:\\Test\\Test\", @"c:\test\test")] - [Row(@"\\Testserver\Test\", @"\\testserver\test")] + [Row(@"C:\\Test\\", @"C:\Test")] + [Row(@"C:\\Test\\Test\", @"C:\Test\Test")] + [Row(@"\\Testserver\Test\", @"\\Testserver\Test")] public void Normalize_Path(string dirty, string clean) { var result = Parser.NormalizePath(dirty); diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 8fa66af44..115d6abfa 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -171,15 +171,18 @@ namespace NzbDrone.Core { //Setup the default providers in the Providers table - string nzbMatrixRss = "http://rss.nzbmatrix.com/rss.php?page=download&username={USERNAME}&apikey={APIKEY}&subcat=6&english=1"; + string nzbMatrixRss = "http://rss.nzbmatrix.com/rss.php?page=download&username={USERNAME}&apikey={APIKEY}&subcat=6,41&english=1"; + string nzbMatrixApi = "http://rss.nzbmatrix.com/rss.php?page=download&username={USERNAME}&apikey={APIKEY}&subcat=6,41&english=1&age={AGE}&term={TERM}"; string nzbsOrgRss = "http://nzbs.org/rss.php?type=1&dl=1&num=100&i={UID}&h={HASH}"; + string nzbsOrgApi = String.Empty; string nzbsrusRss = "http://www.nzbsrus.com/rssfeed.php?cat=91,75&i={UID}&h={HASH}"; + string nzbsrusApi = String.Empty; var nzbMatrixIndexer = new Indexer { IndexerName = "NzbMatrix", RssUrl = nzbMatrixRss, - ApiUrl = String.Empty, + ApiUrl = nzbMatrixApi, Order = 1 }; @@ -187,7 +190,7 @@ namespace NzbDrone.Core { IndexerName = "NzbsOrg", RssUrl = nzbsOrgRss, - ApiUrl = String.Empty, + ApiUrl = nzbsOrgApi, Order = 2 }; @@ -195,7 +198,7 @@ namespace NzbDrone.Core { IndexerName = "Nzbsrus", RssUrl = nzbsrusRss, - ApiUrl = String.Empty, + ApiUrl = nzbsrusApi, Order = 3 }; diff --git a/NzbDrone.Core/Helpers/SceneNameHelper.cs b/NzbDrone.Core/Helpers/SceneNameHelper.cs new file mode 100644 index 000000000..fd3caf08b --- /dev/null +++ b/NzbDrone.Core/Helpers/SceneNameHelper.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Core.Model; + +namespace NzbDrone.Core.Helpers +{ + public static class SceneNameHelper + { + private static List _sceneNameMappings = new List + { + new SceneNameModel { SeriesId = 72546, Name = "CSI" }, + new SceneNameModel { SeriesId = 73696, Name = "CSI New York" }, + new SceneNameModel { SeriesId = 73696, Name = "CSI NY" }, + new SceneNameModel { SeriesId = 110381, Name = "Archer" }, + new SceneNameModel { SeriesId = 83897, Name = "Life After People The Series" }, + new SceneNameModel { SeriesId = 83897, Name = "Life After People" }, + new SceneNameModel { SeriesId = 80552, Name = "Kitchen Nightmares US" }, + new SceneNameModel { SeriesId = 71256, Name = "The Daily Show" }, + new SceneNameModel { SeriesId = 71256, Name = "The Daily Show with Jon Stewart" }, + new SceneNameModel { SeriesId = 75692, Name = "Law and Order SVU" }, + new SceneNameModel { SeriesId = 75692, Name = "Law and Order Special Victims Unit" }, + new SceneNameModel { SeriesId = 71489, Name = "Law and Order Criminal Intent" }, + new SceneNameModel { SeriesId = 71489, Name = "Law and Order CI" }, + new SceneNameModel { SeriesId = 79590, Name = "Dancing With The Stars US" }, + new SceneNameModel { SeriesId = 73387, Name = "Craig Ferguson" }, + new SceneNameModel { SeriesId = 85355, Name = "Jimmy Fallon" }, + new SceneNameModel { SeriesId = 75088, Name = "David Letterman" }, + new SceneNameModel { SeriesId = 76706, Name = "Big Brother US" }, + new SceneNameModel { SeriesId = 105521, Name = "The Colony" }, + new SceneNameModel { SeriesId = 105521, Name = "The Colony US" }, + new SceneNameModel { SeriesId = 76235, Name = "Americas Funniest Home Videos" }, + new SceneNameModel { SeriesId = 76235, Name = "AFHV" }, + new SceneNameModel { SeriesId = 139941, Name = "Childrens Hospital US" }, + new SceneNameModel { SeriesId = 139941, Name = "Childrens Hospital" }, + new SceneNameModel { SeriesId = 83123, Name = "Merlin" }, + new SceneNameModel { SeriesId = 83123, Name = "Merlin 2008" }, + new SceneNameModel { SeriesId = 76779, Name = "WWE Monday Night RAW" }, + new SceneNameModel { SeriesId = 164951, Name = "Shit My Dad Says" }, + new SceneNameModel { SeriesId = 83714, Name = "Genius with Dave Gorman" }, + new SceneNameModel { SeriesId = 168161, Name = "Law and Order Los Angeles" }, + new SceneNameModel { SeriesId = 168161, Name = "Law and Order LA" }, + new SceneNameModel { SeriesId = 77526, Name = "Star Trek TOS" }, + new SceneNameModel { SeriesId = 72073, Name = "Star Trek DS9" }, + new SceneNameModel { SeriesId = 72194, Name = "Ellen Degeneres" }, + new SceneNameModel { SeriesId = 72194, Name = "Ellen Degeneres" }, + new SceneNameModel { SeriesId = 195831, Name = "Drinking Made Easy" }, + new SceneNameModel { SeriesId = 195831, Name = "Zane Lampreys Drinking Made Easy" }, + new SceneNameModel { SeriesId = 76133, Name = "Poirot" }, + new SceneNameModel { SeriesId = 76133, Name = "Agatha Christies Poirot" }, + new SceneNameModel { SeriesId = 70870, Name = "The Real World Road Rules Challenge" }, + new SceneNameModel { SeriesId = 70870, Name = "The Challenge Cutthroat" }, + new SceneNameModel { SeriesId = 77444, Name = "This Old House Program" }, + new SceneNameModel { SeriesId = 73290, Name = "60 Minutes US" }, + new SceneNameModel { SeriesId = 194751, Name = "Conan" }, + new SceneNameModel { SeriesId = 194751, Name = "Conan 2010" }, + new SceneNameModel { SeriesId = 164451, Name = "Carlos 2010" }, + new SceneNameModel { SeriesId = 70726, Name = "Babalon 5" }, + new SceneNameModel { SeriesId = 70726, Name = "Babalon5" }, + new SceneNameModel { SeriesId = 83714, Name = "Genius" }, + new SceneNameModel { SeriesId = 83714, Name = "Genius With Dave Gormand" }, + new SceneNameModel { SeriesId = 212571, Name = "Come Fly With Me 2010" }, + new SceneNameModel { SeriesId = 81563, Name = "Border Security" }, + new SceneNameModel { SeriesId = 81563, Name = "Border Security Australias Frontline" }, + new SceneNameModel { SeriesId = 172381, Name = "Silent Library US" }, + new SceneNameModel { SeriesId = 131791, Name = "Sci-Fi Science" }, + new SceneNameModel { SeriesId = 80646, Name = "Frontline" }, + new SceneNameModel { SeriesId = 80646, Name = "Frontline US" }, + new SceneNameModel { SeriesId = 189931, Name = "RBT AU" }, + new SceneNameModel { SeriesId = 73255, Name = "House" }, + new SceneNameModel { SeriesId = 73255, Name = "House MD" }, + new SceneNameModel { SeriesId = 73244, Name = "The Office" }, + new SceneNameModel { SeriesId = 73244, Name = "The Office US" }, + }; + + public static int FindByName(string seriesName) + { + var map = _sceneNameMappings.Single(s => s.Name == seriesName); + + if (map == null) + return 0; + + return map.SeriesId; + } + + public static List FindById(int seriesId) + { + List results = new List(); + + var maps = _sceneNameMappings.Where(s => s.SeriesId == seriesId); + + foreach (var map in maps) + results.Add(map.Name); + + return results; + } + + } +} diff --git a/NzbDrone.Core/Model/SceneNameModel.cs b/NzbDrone.Core/Model/SceneNameModel.cs new file mode 100644 index 000000000..595abd3a3 --- /dev/null +++ b/NzbDrone.Core/Model/SceneNameModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model +{ + public class SceneNameModel + { + public string Name { get; set; } + public int SeriesId { get; set; } + } +} diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index fa7378821..7ff84ac3e 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -158,6 +158,7 @@ + @@ -174,9 +175,12 @@ + + + diff --git a/NzbDrone.Core/Providers/BacklogProvider.cs b/NzbDrone.Core/Providers/BacklogProvider.cs new file mode 100644 index 000000000..cf5cb4e4a --- /dev/null +++ b/NzbDrone.Core/Providers/BacklogProvider.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using NLog; +using NzbDrone.Core.Helpers; +using NzbDrone.Core.Model; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Repository; +using Rss; + +namespace NzbDrone.Core.Providers +{ + public class BacklogProvider : IBacklogProvider + { + private readonly ISeriesProvider _seriesProvider; + private readonly INotificationProvider _notificationProvider; + private readonly IConfigProvider _configProvider; + private readonly IIndexerProvider _indexerProvider; + private readonly IRssProvider _rssProvider; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private List _seriesList; + private Thread _backlogThread; + private ProgressNotification _backlogSearchNotification; + + public BacklogProvider(ISeriesProvider seriesProvider, INotificationProvider notificationProvider, + IConfigProvider configProvider, IIndexerProvider indexerProvider, + IRssProvider rssProvider) + { + _seriesProvider = seriesProvider; + _notificationProvider = notificationProvider; + _configProvider = configProvider; + _indexerProvider = indexerProvider; + _rssProvider = rssProvider; + + _seriesList = new List(); + } + + #region IBacklogProvider Members + + public bool StartSearch() + { + Logger.Debug("Backlog Search Requested"); + if (_backlogThread == null || !_backlogThread.IsAlive) + { + Logger.Debug("Initializing Backlog Search"); + _backlogThread = new Thread(PerformSearch) + { + Name = "BacklogSearch", + Priority = ThreadPriority.Lowest + }; + + _seriesList.AddRange(_seriesProvider.GetAllSeries()); + _backlogThread.Start(); + } + else + { + Logger.Warn("Backlog Search already in progress. Ignoring request."); + + //return false if backlog search was already running + return false; + } + + //return true if backlog search has started + return true; + } + + public bool StartSearch(int seriesId) + { + //Get the series + //Start new Thread if one isn't already started + + Logger.Debug("Backlog Search Requested"); + if (_backlogThread == null || !_backlogThread.IsAlive) + { + Logger.Debug("Initializing Backlog Search"); + _backlogThread = new Thread(PerformSearch) + { + Name = "BacklogSearch", + Priority = ThreadPriority.Lowest + }; + + var series = _seriesProvider.GetSeries(seriesId); + + if (series == null) + { + Logger.Debug("Invalid Series - Not starting Backlog Search"); + return false; + } + + _seriesList.Add(series); + _backlogThread.Start(); + } + else + { + Logger.Warn("Backlog Search already in progress. Ignoring request."); + + //return false if backlog search was already running + return false; + } + + //return true if backlog search has started + return true; + } + + #endregion + + private void PerformSearch() + { + try + { + using (_backlogSearchNotification = new ProgressNotification("Series Scan")) + { + _notificationProvider.Register(_backlogSearchNotification); + _backlogSearchNotification.CurrentStatus = "Starting Backlog Search"; + _backlogSearchNotification.ProgressMax = _seriesList.Count; + + foreach (var series in _seriesList) + { + try + { + //Do the searching here + _backlogSearchNotification.CurrentStatus = String.Format("Backlog Searching For: {0}", series.Title); + + var sceneNames = SceneNameHelper.FindById(series.SeriesId); + + if (sceneNames.Count < 1) + sceneNames.Add(series.Title); + + foreach (var season in series.Seasons) + { + var episodesWithoutFiles = season.Episodes.Where(e => e.EpisodeFileId == 0); + + if (season.Episodes.Count() == episodesWithoutFiles.Count()) + { + //Whole season needs to be grabbed, look for the whole season first + //Lookup scene name using seriesId + + foreach (var sceneName in sceneNames) + { + var searchString = String.Format("{0} Season {1}", sceneName, + season.SeasonNumber); + + foreach (var i in _indexerProvider.EnabledIndexers()) + { + //Get the users URL + GetUsersUrl(i, searchString); + + //If the url still contains '{' & '}' the user probably hasn't configured the indexer settings + if (i.ApiUrl.Contains("{") && i.ApiUrl.Contains("}")) + { + Logger.Debug("Unable to Sync {0}. User Information has not been configured.", i.IndexerName); + continue; //Skip this indexer + } + + var indexer = new FeedInfoModel(i.IndexerName, i.ApiUrl); + + var feedItems = _rssProvider.GetFeed(indexer); + + if (feedItems.Count() == 0) + { + Logger.Debug("Failed to download Backlog Search URL: {0}", indexer.Name); + continue; //No need to process anything else + } + + foreach (RssItem item in feedItems) + { + NzbInfoModel nzb = Parser.ParseNzbInfo(indexer, item); + QueueSeasonIfWanted(nzb, i); + } + + } + } + } + + else + { + //Grab the episodes 1-by-1 (or in smaller chunks) + + } + + } + //Done searching for each episode + } + + catch (Exception ex) + { + Logger.WarnException(ex.Message, ex); + } + + _backlogSearchNotification.ProgressValue++; + } + + _backlogSearchNotification.CurrentStatus = "Backlog Search Completed"; + Logger.Info("Backlog Search has successfully completed."); + Thread.Sleep(3000); + _backlogSearchNotification.Status = ProgressNotificationStatus.Completed; + } + } + + catch (Exception ex) + { + Logger.WarnException(ex.Message, ex); + } + } + + private void GetUsersUrl(Indexer indexer, string searchString) + { + if (indexer.IndexerName == "NzbMatrix") + { + var nzbMatrixUsername = _configProvider.GetValue("NzbMatrixUsername", String.Empty, false); + var nzbMatrixApiKey = _configProvider.GetValue("NzbMatrixApiKey", String.Empty, false); + var retention = Convert.ToInt32(_configProvider.GetValue("Retention", String.Empty, false)); + + if (!String.IsNullOrEmpty(nzbMatrixUsername) && !String.IsNullOrEmpty(nzbMatrixApiKey)) + indexer.ApiUrl = indexer.ApiUrl.Replace("{USERNAME}", nzbMatrixUsername).Replace("{APIKEY}", nzbMatrixApiKey).Replace("{AGE}", retention.ToString()).Replace("{TERM}", searchString); + + //Todo: Perform validation at the config level so a user is unable to enable a provider until user details are provided + return; + } + + if (indexer.IndexerName == "NzbsOrg") + { + var nzbsOrgUId = _configProvider.GetValue("NzbsOrgUId", String.Empty, false); + var nzbsOrgHash = _configProvider.GetValue("NzbsOrgHash", String.Empty, false); + + if (!String.IsNullOrEmpty(nzbsOrgUId) && !String.IsNullOrEmpty(nzbsOrgHash)) + indexer.RssUrl = indexer.RssUrl.Replace("{UID}", nzbsOrgUId).Replace("{HASH}", nzbsOrgHash); + + //Todo: Perform validation at the config level so a user is unable to enable a provider until user details are provided + return; + } + + if (indexer.IndexerName == "NzbsOrg") + { + var nzbsrusUId = _configProvider.GetValue("NzbsrusUId", String.Empty, false); + var nzbsrusHash = _configProvider.GetValue("NzbsrusHash", String.Empty, false); + + if (!String.IsNullOrEmpty(nzbsrusUId) && !String.IsNullOrEmpty(nzbsrusHash)) + indexer.RssUrl = indexer.RssUrl.Replace("{UID}", nzbsrusUId).Replace("{HASH}", nzbsrusHash); + + //Todo: Perform validation at the config level so a user is unable to enable a provider until user details are provided + return; + } + + return; //Currently other providers do not require user information to be substituted, simply return + } + + private void QueueSeasonIfWanted(NzbInfoModel nzb, Indexer indexer) + { + //Do we want this item? + try + { + if (nzb.IsPassworded()) + { + Logger.Debug("Skipping Passworded Report {0}", nzb.Title); + return; + } + + //Need to get REGEX that will handle "Show Name Season 1 quality" + nzb.TitleFix = String.Empty; + nzb.TitleFix = String.Format("{0} [{1}]", nzb.TitleFix, nzb.Quality); //Add Quality to the titleFix + + //Check that we want this quality + var quality = Parser.ParseQuality(nzb.Title); + } + + catch (Exception ex) + { + Logger.DebugException(ex.Message, ex); + } + } + } +} diff --git a/NzbDrone.Core/Providers/DiskProvider.cs b/NzbDrone.Core/Providers/DiskProvider.cs index e87263262..529440563 100644 --- a/NzbDrone.Core/Providers/DiskProvider.cs +++ b/NzbDrone.Core/Providers/DiskProvider.cs @@ -49,6 +49,12 @@ namespace NzbDrone.Core.Providers File.Move(sourcePath, destinationPath); } + public string GetFolderName(string path) + { + var di = new DirectoryInfo(path); + return di.Name; + } + #endregion } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/IBacklogProvider.cs b/NzbDrone.Core/Providers/IBacklogProvider.cs new file mode 100644 index 000000000..8afaf62cc --- /dev/null +++ b/NzbDrone.Core/Providers/IBacklogProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Providers +{ + public interface IBacklogProvider + { + //Will provide Backlog Search functionality + + bool StartSearch(); + bool StartSearch(int seriesId); + } +} diff --git a/NzbDrone.Core/Providers/IDiskProvider.cs b/NzbDrone.Core/Providers/IDiskProvider.cs index bf17cf1d1..242721f27 100644 --- a/NzbDrone.Core/Providers/IDiskProvider.cs +++ b/NzbDrone.Core/Providers/IDiskProvider.cs @@ -13,5 +13,6 @@ namespace NzbDrone.Core.Providers long GetSize(string path); void DeleteFile(string path); void RenameFile(string sourcePath, string destinationPath); + string GetFolderName(string path); } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/ISyncProvider.cs b/NzbDrone.Core/Providers/ISyncProvider.cs index 8efc55b44..1ab4865e1 100644 --- a/NzbDrone.Core/Providers/ISyncProvider.cs +++ b/NzbDrone.Core/Providers/ISyncProvider.cs @@ -8,6 +8,7 @@ namespace NzbDrone.Core.Providers { bool BeginSyncUnmappedFolders(List unmapped); bool BeginAddNewSeries(string dir, int seriesId, string seriesName); + bool BeginAddExistingSeries(string path, int seriesId); List GetUnmappedFolders(string path); } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/IndexerProvider.cs b/NzbDrone.Core/Providers/IndexerProvider.cs index 1af472693..d7b94fa87 100644 --- a/NzbDrone.Core/Providers/IndexerProvider.cs +++ b/NzbDrone.Core/Providers/IndexerProvider.cs @@ -14,13 +14,15 @@ namespace NzbDrone.Core.Providers { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly IRepository _sonicRepo; + private readonly IConfigProvider _configProvider; - public IndexerProvider(IRepository sonicRepo) + public IndexerProvider(IRepository sonicRepo, IConfigProvider configProvider) { _sonicRepo = sonicRepo; + _configProvider = configProvider; } - #region IIndexerProvider + #region IIndexerProvider Members public List AllIndexers() { diff --git a/NzbDrone.Core/Providers/RssSyncProvider.cs b/NzbDrone.Core/Providers/RssSyncProvider.cs index 456f888d3..3299a2695 100644 --- a/NzbDrone.Core/Providers/RssSyncProvider.cs +++ b/NzbDrone.Core/Providers/RssSyncProvider.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading; using NLog; +using NzbDrone.Core.Helpers; using NzbDrone.Core.Model; using NzbDrone.Core.Model.Notification; using NzbDrone.Core.Repository; @@ -146,8 +147,14 @@ namespace NzbDrone.Core.Providers if (series == null) { - Logger.Debug("Show is not being watched: {0}", episodeParseResults[0].SeriesTitle); - return; + //If we weren't able to find a title using the clean name, lets try again looking for a scene name + series = _series.GetSeries(SceneNameHelper.FindByName(episodeParseResults[0].SeriesTitle)); + + if (series == null) + { + Logger.Debug("Show is not being watched: {0}", episodeParseResults[0].SeriesTitle); + return; + } } Logger.Debug("Show is being watched: {0}", series.Title); diff --git a/NzbDrone.Core/Providers/SyncProvider.cs b/NzbDrone.Core/Providers/SyncProvider.cs index 030b741fa..64bc283ce 100644 --- a/NzbDrone.Core/Providers/SyncProvider.cs +++ b/NzbDrone.Core/Providers/SyncProvider.cs @@ -126,6 +126,37 @@ namespace NzbDrone.Core.Providers return true; } + public bool BeginAddExistingSeries(string path, int seriesId) + { + Logger.Debug("User has requested adding of new series"); + if (_seriesSyncThread == null || !_seriesSyncThread.IsAlive) + { + Logger.Debug("Initializing background add of of series folder."); + _seriesSyncThread = new Thread(SyncUnmappedFolders) + { + Name = "SyncUnmappedFolders", + Priority = ThreadPriority.Lowest + }; + + _syncList = new List(); + + //Add it to the list so it will be processed + _syncList.Add(new SeriesMappingModel { Path = path, TvDbId = seriesId }); + + _seriesSyncThread.Start(); + } + else + { + Logger.Warn("Series folder scan already in progress. Ignoring request."); + + //return false if sync was already running, then we can tell the user to try again later + return false; + } + + //return true if sync has started + return true; + } + private void SyncUnmappedFolders() { Logger.Info("Starting Series folder scan"); @@ -167,6 +198,7 @@ namespace NzbDrone.Core.Providers _episodeProvider.RefreshEpisodeInfo(mappedSeries.Id); _seriesSyncNotification.CurrentStatus = String.Format("{0}: finding episodes on disk...", mappedSeries.SeriesName); _mediaFileProvider.Scan(_seriesProvider.GetSeries(mappedSeries.Id)); + //Todo: Launch Backlog search for this series _backlogProvider.StartSearch(mappedSeries.Id); } else { diff --git a/NzbDrone.Web/Content/style.css b/NzbDrone.Web/Content/style.css index 5237c18d1..dd0e83363 100644 --- a/NzbDrone.Web/Content/style.css +++ b/NzbDrone.Web/Content/style.css @@ -234,4 +234,11 @@ input[type="text"]:hover { border: 1px solid #f00; background: #eef; +} + +/* Add Series */ + +.tvDbSearchResults +{ + width: 400px; } \ No newline at end of file diff --git a/NzbDrone.Web/Controllers/SeriesController.cs b/NzbDrone.Web/Controllers/SeriesController.cs index da1e677d8..e70d55482 100644 --- a/NzbDrone.Web/Controllers/SeriesController.cs +++ b/NzbDrone.Web/Controllers/SeriesController.cs @@ -28,6 +28,7 @@ namespace NzbDrone.Web.Controllers private readonly IRenameProvider _renameProvider; private readonly IRootDirProvider _rootDirProvider; private readonly ITvDbProvider _tvDbProvider; + private readonly IDiskProvider _diskProvider; // // GET: /Series/ @@ -36,7 +37,7 @@ namespace NzbDrone.Web.Controllers IEpisodeProvider episodeProvider, IRssSyncProvider rssSyncProvider, IQualityProvider qualityProvider, IMediaFileProvider mediaFileProvider, IRenameProvider renameProvider, IRootDirProvider rootDirProvider, - ITvDbProvider tvDbProvider) + ITvDbProvider tvDbProvider, IDiskProvider diskProvider) { _seriesProvider = seriesProvider; _episodeProvider = episodeProvider; @@ -47,6 +48,7 @@ namespace NzbDrone.Web.Controllers _renameProvider = renameProvider; _rootDirProvider = rootDirProvider; _tvDbProvider = tvDbProvider; + _diskProvider = diskProvider; } public ActionResult Index() @@ -67,7 +69,25 @@ namespace NzbDrone.Web.Controllers public ActionResult AddNew() { - return View(); + ViewData["RootDirs"] = _rootDirProvider.GetAll(); + ViewData["DirSep"] = Path.DirectorySeparatorChar; + + var model = new AddNewSeriesModel + { + DirectorySeparatorChar = Path.DirectorySeparatorChar.ToString(), + RootDirectories = _rootDirProvider.GetAll() + }; + + return View(model); + } + + public ActionResult AddExistingManual(string path) + { + var model = new AddExistingManualModel(); + model.Path = path; + model.FolderName = _diskProvider.GetFolderName(path); + + return View(model); } public ActionResult RssSync() @@ -137,6 +157,7 @@ namespace NzbDrone.Web.Controllers { IsWanted = true, Path = unmappedFolder, + PathEncoded = Url.Encode(unmappedFolder), TvDbId = tvDbSeries.Id, TvDbName = tvDbSeries.SeriesName }); @@ -181,26 +202,35 @@ namespace NzbDrone.Web.Controllers return Content("Unable to add new series, please wait for previous scans to complete first."); } + public ActionResult AddExistingSeries(string path, int seriesId) + { + //Get TVDB Series Name + //Create new folder for series + //Add the new series to the Database + + if (_syncProvider.BeginAddExistingSeries(path, seriesId)) + return Content("Manual adding of existing series has started"); + + return Content("Unable to add existing series, please wait for previous scans to complete first."); + } + public ActionResult SearchForSeries(string seriesName) { var model = new List(); //Get Results from TvDb and convert them to something we can use. - //foreach (var tvdbSearchResult in _tvDbProvider.SearchSeries(seriesName)) - //{ - // model.Add(new SeriesSearchResultModel - // { - // TvDbId = tvdbSearchResult.Id, - // TvDbName = tvdbSearchResult.SeriesName, - // FirstAired = tvdbSearchResult.FirstAired - // }); - //} - - ViewData["RootDirs"] = _rootDirProvider.GetAll(); - ViewData["DirSep"] = Path.DirectorySeparatorChar; + foreach (var tvdbSearchResult in _tvDbProvider.SearchSeries(seriesName)) + { + model.Add(new SeriesSearchResultModel + { + TvDbId = tvdbSearchResult.Id, + TvDbName = tvdbSearchResult.SeriesName, + FirstAired = tvdbSearchResult.FirstAired + }); + } - model.Add(new SeriesSearchResultModel{ TvDbId = 12345, TvDbName = "30 Rock", FirstAired = DateTime.Today }); - model.Add(new SeriesSearchResultModel { TvDbId = 65432, TvDbName = "The Office (US)", FirstAired = DateTime.Today.AddDays(-100) }); + //model.Add(new SeriesSearchResultModel{ TvDbId = 12345, TvDbName = "30 Rock", FirstAired = DateTime.Today }); + //model.Add(new SeriesSearchResultModel { TvDbId = 65432, TvDbName = "The Office (US)", FirstAired = DateTime.Today.AddDays(-100) }); return PartialView("SeriesSearchResults", model); } diff --git a/NzbDrone.Web/Models/AddExistingManualModel.cs b/NzbDrone.Web/Models/AddExistingManualModel.cs new file mode 100644 index 000000000..bb1090daf --- /dev/null +++ b/NzbDrone.Web/Models/AddExistingManualModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace NzbDrone.Web.Models +{ + public class AddExistingManualModel + { + public string Path { get; set; } + public string FolderName { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Web/Models/AddExistingSeriesModel.cs b/NzbDrone.Web/Models/AddExistingSeriesModel.cs index bde01e0f1..0c64da56d 100644 --- a/NzbDrone.Web/Models/AddExistingSeriesModel.cs +++ b/NzbDrone.Web/Models/AddExistingSeriesModel.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Web.Models { public bool IsWanted { get; set; } public string Path { get; set; } + public string PathEncoded { get; set; } public int TvDbId { get; set; } public string TvDbName { get; set; } } diff --git a/NzbDrone.Web/Models/AddNewSeriesModel.cs b/NzbDrone.Web/Models/AddNewSeriesModel.cs index e2da0d96b..705967573 100644 --- a/NzbDrone.Web/Models/AddNewSeriesModel.cs +++ b/NzbDrone.Web/Models/AddNewSeriesModel.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web; +using NzbDrone.Core.Repository; namespace NzbDrone.Web.Models { @@ -14,5 +15,9 @@ namespace NzbDrone.Web.Models [DisplayName("Single Series Path")] [DisplayFormat(ConvertEmptyStringToNull = false)] public string SeriesName { get; set; } + + public string DirectorySeparatorChar { get; set; } + + public List RootDirectories { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index 502fe2f66..6d3d0db21 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -90,6 +90,7 @@ + @@ -278,6 +279,7 @@ + diff --git a/NzbDrone.Web/Views/Series/AddExisting.aspx b/NzbDrone.Web/Views/Series/AddExisting.aspx index 84ece5697..512c31119 100644 --- a/NzbDrone.Web/Views/Series/AddExisting.aspx +++ b/NzbDrone.Web/Views/Series/AddExisting.aspx @@ -4,7 +4,7 @@ <%@ Import Namespace="NzbDrone.Web.Models" %> - Add Existing + Add Existing Series <% @@ -16,6 +16,7 @@ - - <% - Html.Telerik().Grid().Name("Unmapped_Series_Folders") - .TableHtmlAttributes(new { id = "UnmappedSeriesGrid" }) - .Columns(columns => - { - columns.Bound(c => c.IsWanted).ClientTemplate("") - .Width(20).Title("") - .HtmlAttributes(new { style = "text-align:center" }); - - columns.Bound(c => c.Path); - columns.Bound(c => c.TvDbName); - }) - .DataBinding(d => d.Ajax().Select("_AjaxUnmappedFoldersGrid", "Series")) - .ClientEvents(events => events.OnRowDataBound("Grid_onRowDataBound")) - .Footer(false) - .Render(); - %> -

- -

+
-
+ +
+ + <% + Html.RenderPartial("SubMenu"); + %> + + + +
+

<%= Html.Label(Model.Path) %>

+
+ + <%= Html.Label("Enter a Series Name") %> + <%= Html.TextBoxFor(m => m.FolderName, new { id="existing_series_id" }) %> + <%= Html.TextBoxFor(m => m.Path, new { id ="series_path", style="display:none" }) %> + +

+ +

+ +
+ + + +
+ + +
+ +
diff --git a/NzbDrone.Web/Views/Series/AddNew.aspx b/NzbDrone.Web/Views/Series/AddNew.aspx index 0db4a4201..7bf3bba21 100644 --- a/NzbDrone.Web/Views/Series/AddNew.aspx +++ b/NzbDrone.Web/Views/Series/AddNew.aspx @@ -1,9 +1,17 @@ <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> <%@ Import Namespace="NzbDrone.Web.Models" %> <%@ Import Namespace="Telerik.Web.Mvc.UI" %> +<%@ Import Namespace="NzbDrone.Core.Repository" %> Add New Series + + + <% @@ -16,30 +24,95 @@ <%= Html.TextBox("new_series_name", String.Empty, new { id="new_series_id" }) %>

- +

+ + +
+
diff --git a/NzbDrone.Web/Views/Series/SeriesSearchResults.ascx b/NzbDrone.Web/Views/Series/SeriesSearchResults.ascx index 46d894cbe..c80e65f39 100644 --- a/NzbDrone.Web/Views/Series/SeriesSearchResults.ascx +++ b/NzbDrone.Web/Views/Series/SeriesSearchResults.ascx @@ -2,9 +2,16 @@ <%@ Import Namespace="NzbDrone.Core.Repository" %>
-
+
Search Results + <% if (Model.Count == 0) + { %> + No results found for the series name + <% } + %> + + <% int r = 0; %> <% foreach (var result in Model) { %> @@ -17,75 +24,4 @@ <% } %>
-
- - - -
- - \ No newline at end of file + \ No newline at end of file