fixed diskscan

removed all stored status fields from episode
pull/3113/head
kay.one 11 years ago
parent feb947fb74
commit cbe4be814c

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.Extensions;
using NzbDrone.Common.Composition;
using NzbDrone.Common.Messaging;
namespace NzbDrone.Api.Commands
@ -9,20 +10,22 @@ namespace NzbDrone.Api.Commands
public class CommandModule : NzbDroneRestModule<CommandResource>
{
private readonly IMessageAggregator _messageAggregator;
private readonly IEnumerable<ICommand> _commands;
private readonly IContainer _container;
public CommandModule(IMessageAggregator messageAggregator, IEnumerable<ICommand> commands)
public CommandModule(IMessageAggregator messageAggregator, IContainer container)
{
_messageAggregator = messageAggregator;
_commands = commands;
_container = container;
CreateResource = RunCommand;
}
private CommandResource RunCommand(CommandResource resource)
{
var commandType = _commands.Single(c => c.GetType().Name.Replace("Command", "").Equals(resource.Command, StringComparison.InvariantCultureIgnoreCase))
.GetType();
var commandType =
_container.GetImplementations(typeof(ICommand))
.Single(c => c.Name.Replace("Command", "")
.Equals(resource.Command, StringComparison.InvariantCultureIgnoreCase));
var command = Request.Body.FromJson<ICommand>(commandType);

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TinyIoC;
namespace NzbDrone.Common.Composition
@ -7,10 +8,12 @@ namespace NzbDrone.Common.Composition
public class Container : IContainer
{
private readonly TinyIoCContainer _container;
private readonly List<Type> _loadedTypes;
public Container(TinyIoCContainer container)
public Container(TinyIoCContainer container, List<Type> loadedTypes)
{
_container = container;
_loadedTypes = loadedTypes;
_container.Register<IContainer>(this);
}
@ -92,5 +95,15 @@ namespace NzbDrone.Common.Composition
{
return _container.CanResolve(type);
}
public IEnumerable<Type> GetImplementations(Type contractType)
{
return _loadedTypes
.Where(implementation =>
contractType.IsAssignableFrom(implementation) &&
!implementation.IsInterface &&
!implementation.IsAbstract
);
}
}
}

@ -18,8 +18,6 @@ namespace NzbDrone.Common.Composition
protected ContainerBuilderBase(params string[] assemblies)
{
Container = new Container(new TinyIoCContainer());
_loadedTypes = new List<Type>();
foreach (var assembly in assemblies)
@ -27,6 +25,7 @@ namespace NzbDrone.Common.Composition
_loadedTypes.AddRange(Assembly.Load(assembly).GetTypes());
}
Container = new Container(new TinyIoCContainer(), _loadedTypes);
AutoRegisterInterfaces();
}
@ -52,7 +51,7 @@ namespace NzbDrone.Common.Composition
private void AutoRegisterImplementations(Type contractType)
{
var implementations = GetImplementations(contractType).Where(c => !c.IsGenericTypeDefinition).ToList();
var implementations = Container.GetImplementations(contractType).Where(c => !c.IsGenericTypeDefinition).ToList();
@ -84,14 +83,5 @@ namespace NzbDrone.Common.Composition
}
}
private IEnumerable<Type> GetImplementations(Type contractType)
{
return _loadedTypes
.Where(implementation =>
contractType.IsAssignableFrom(implementation) &&
!implementation.IsInterface &&
!implementation.IsAbstract
);
}
}
}

@ -26,5 +26,7 @@ namespace NzbDrone.Common.Composition
void Register(Type registrationType, object instance);
void RegisterAll(Type registrationType, IEnumerable<Type> implementationList);
bool IsTypeRegistered(Type type);
IEnumerable<Type> GetImplementations(Type contractType);
}
}

@ -34,8 +34,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
firstFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, true), DateAdded = DateTime.Now };
secondFile = new EpisodeFile { Quality = new QualityModel(Quality.Bluray1080p, true), DateAdded = DateTime.Now };
var singleEpisodeList = new List<Episode> { new Episode { EpisodeFile = firstFile }, new Episode { EpisodeFile = null } };
var doubleEpisodeList = new List<Episode> { new Episode { EpisodeFile = firstFile }, new Episode { EpisodeFile = secondFile }, new Episode { EpisodeFile = null } };
var singleEpisodeList = new List<Episode> { new Episode { EpisodeFile = firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } };
var doubleEpisodeList = new List<Episode> { new Episode { EpisodeFile = firstFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = secondFile, EpisodeFileId = 1 }, new Episode { EpisodeFile = null } };
var fakeSeries = Builder<Series>.CreateNew()
.With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p })
@ -66,6 +66,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
secondFile.Quality = new QualityModel(Quality.SDTV);
}
[Test]
public void should_return_true_if_episode_has_no_existing_file()
{
parseResultSingle.Episodes.ForEach(c => c.EpisodeFileId = 0);
_upgradeDisk.IsSatisfiedBy(parseResultSingle).Should().BeTrue();
}
[Test]
public void should_return_true_if_single_episode_doesnt_exist_on_disk()
{

@ -11,79 +11,15 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test
{
[TestFixture]
public class EpisodeStatusTest : CoreTest
{
[TestCase(1, false, false, EpisodeStatuses.NotAired)]
[TestCase(-2, false, false, EpisodeStatuses.Missing)]
[TestCase(0, false, false, EpisodeStatuses.AirsToday)]
[TestCase(1, true, false, EpisodeStatuses.Ready)]
public void no_grab_date(int offsetDays, bool hasEpisodes, bool ignored, EpisodeStatuses status)
{
Episode episode = Builder<Episode>.CreateNew()
.With(e => e.AirDate = DateTime.Now.AddDays(offsetDays))
.With(e => e.Ignored = ignored)
.With(e => e.GrabDate = null)
.Build();
if (hasEpisodes)
{
episode.EpisodeFile = new EpisodeFile();
}
episode.Status.Should().Be(status);
}
[TestCase(1, false, false, EpisodeStatuses.Missing)]
[TestCase(-2, false, false, EpisodeStatuses.Missing)]
[TestCase(1, true, false, EpisodeStatuses.Ready)]
public void old_grab_date(int offsetDays, bool hasEpisodes, bool ignored,
EpisodeStatuses status)
{
Episode episode = Builder<Episode>.CreateNew()
.With(e => e.Ignored = ignored)
.With(e => e.GrabDate = DateTime.Now.AddDays(-2).AddHours(-1))
.With(e => e.AirDate = DateTime.Today.AddDays(-2))
.Build();
if (hasEpisodes)
{
episode.EpisodeFile = new EpisodeFile();
}
episode.Status.Should().Be(status);
}
[TestCase(1, false, false, EpisodeStatuses.Downloading)]
[TestCase(-2, false, false, EpisodeStatuses.Downloading)]
[TestCase(1, true, false, EpisodeStatuses.Ready)]
[TestCase(1, true, true, EpisodeStatuses.Ready)]
[TestCase(1, false, true, EpisodeStatuses.Downloading)]
public void recent_grab_date(int offsetDays, bool hasEpisodes, bool ignored,
EpisodeStatuses status)
{
Episode episode = Builder<Episode>.CreateNew()
.With(e => e.AirDate = DateTime.Now.AddDays(offsetDays))
.With(e => e.Ignored = ignored)
.With(e => e.GrabDate = DateTime.Now.AddHours(22))
.Build();
if (hasEpisodes)
{
episode.EpisodeFile = new EpisodeFile();
}
episode.Status.Should().Be(status);
}
[TestCase(1, true, true, EpisodeStatuses.Ready)]
public void ignored_episode(int offsetDays, bool ignored, bool hasEpisodes, EpisodeStatuses status)
{
Episode episode = Builder<Episode>.CreateNew()
.With(e => e.AirDate = DateTime.Now.AddDays(offsetDays))
.With(e => e.Ignored = ignored)
.With(e => e.GrabDate = null)
.Build();
if (hasEpisodes)
@ -101,33 +37,11 @@ namespace NzbDrone.Core.Test
Episode episode = Builder<Episode>.CreateNew()
.With(e => e.AirDate = DateTime.Now.AddDays(20))
.With(e => e.Ignored = false)
.With(e => e.GrabDate = null)
.With(e => e.EpisodeFileId = 0)
.Build();
episode.Status.Should().Be(EpisodeStatuses.NotAired);
}
[TestCase(false, false, EpisodeStatuses.Failed, PostDownloadStatusType.Failed)]
[TestCase(false, false, EpisodeStatuses.Unpacking, PostDownloadStatusType.Unpacking)]
[TestCase(true, false, EpisodeStatuses.Ready, PostDownloadStatusType.Failed)]
[TestCase(true, true, EpisodeStatuses.Ready, PostDownloadStatusType.Unpacking)]
public void episode_downloaded_post_download_status_is_used(bool hasEpisodes, bool ignored,
EpisodeStatuses status, PostDownloadStatusType postDownloadStatus)
{
Episode episode = Builder<Episode>.CreateNew()
.With(e => e.Ignored = ignored)
.With(e => e.GrabDate = DateTime.Now.AddHours(22))
.With(e => e.PostDownloadStatus = postDownloadStatus)
.Build();
if (hasEpisodes)
{
episode.EpisodeFile = new EpisodeFile();
}
episode.Status.Should().Be(status);
}
}
}

@ -5,13 +5,14 @@ using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using System.Linq;
namespace NzbDrone.Core.Test.MediaFileTests
{
public class GhostFileCleanupFixture : CoreTest<GhostFileCleanupService>
public class MediaFileTableCleanupServiceFixture : CoreTest<MediaFileTableCleanupService>
{
private void GiveEpisodeFiles(IEnumerable<EpisodeFile> episodeFiles)
@ -30,6 +31,10 @@ namespace NzbDrone.Core.Test.MediaFileTests
Mocker.GetMock<IDiskProvider>()
.Setup(e => e.FileExists(It.Is<String>(c => c != DeletedPath)))
.Returns(true);
Mocker.GetMock<IEpisodeService>()
.Setup(c => c.GetEpisodesByFileId(It.IsAny<int>()))
.Returns(new List<Episode> { new Episode() });
}
[Test]
@ -40,7 +45,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
GiveEpisodeFiles(episodeFiles);
Subject.RemoveNonExistingFiles(0);
Subject.Execute(new CleanMediaFileDb(0));
Mocker.GetMock<IEpisodeService>().Verify(c => c.UpdateEpisode(It.IsAny<Episode>()), Times.Never());
}
@ -55,10 +60,33 @@ namespace NzbDrone.Core.Test.MediaFileTests
GiveEpisodeFiles(episodeFiles);
Subject.RemoveNonExistingFiles(0);
Subject.Execute(new CleanMediaFileDb(0));
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.Is<EpisodeFile>(e => e.Path == DeletedPath)), Times.Exactly(2));
}
[Test]
public void should_delete_files_that_dont_belong_to_any_episodes()
{
var episodeFiles = Builder<EpisodeFile>.CreateListOfSize(10)
.Random(10)
.With(c => c.Path = "ExistingPath")
.Build();
GiveEpisodeFiles(episodeFiles);
GivenFilesAreNotAttachedToEpisode();
Subject.Execute(new CleanMediaFileDb(0));
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.IsAny<EpisodeFile>()), Times.Exactly(10));
}
private void GivenFilesAreNotAttachedToEpisode()
{
Mocker.GetMock<IEpisodeService>()
.Setup(c => c.GetEpisodesByFileId(It.IsAny<int>()))
.Returns(new List<Episode>());
}
}
}

@ -142,7 +142,7 @@
<Compile Include="DecisionEngineTests\LanguageSpecificationFixture.cs" />
<Compile Include="JobTests\TestJobs.cs" />
<Compile Include="MediaCoverTests\MediaCoverServiceFixture.cs" />
<Compile Include="MediaFileTests\GhostFileCleanupFixture.cs" />
<Compile Include="MediaFileTests\MediaFileTableCleanupServiceFixture.cs" />
<Compile Include="MediaFileTests\MediaFileRepositoryFixture.cs" />
<Compile Include="MediaFileTests\EpisodeFileMoverFixture.cs" />
<Compile Include="MetadataSourceTests\TracktProxyFixture.cs" />
@ -160,6 +160,7 @@
<Compile Include="ProviderTests\RecycleBinProviderTests\DeleteFileFixture.cs" />
<Compile Include="ProviderTests\RecycleBinProviderTests\DeleteDirectoryFixture.cs" />
<Compile Include="ProviderTests\PlexProviderTest.cs" />
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesRepositoryReadFixture.cs" />
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesWithoutFilesFixture.cs" />
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesBetweenDatesFixture.cs" />
<Compile Include="TvTests\SeasonProviderTest.cs" />

@ -4,6 +4,7 @@ using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Test.Framework;

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
@ -23,7 +22,7 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
private long _fileSize = 80.Megabytes();
private Series _fakeSeries;
private List<Episode> _fakeEpisodes;
private Episode[] _fakeEpisodes;
private Episode _fakeEpisode;
[SetUp]
@ -35,14 +34,16 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
_fakeEpisode = Builder<Episode>
.CreateNew()
.With(c => c.EpisodeFileId = 0)
.Build();
_fakeEpisodes = Builder<Episode>.CreateListOfSize(2)
.All()
.With(c => c.SeasonNumber = 3)
.With(c => c.EpisodeFileId = 1)
.With(e => e.EpisodeFile = new EpisodeFile())
.BuildList();
.BuildList().ToArray();
GivenNewFile();
@ -69,8 +70,20 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
}
private void GivenEpisodes(IEnumerable<Episode> episodes, QualityModel quality)
private void GivenEpisodes(Episode[] episodes, QualityModel quality)
{
foreach (var episode in episodes)
{
if (episode.EpisodeFile == null)
{
episode.EpisodeFileId = 0;
}
else
{
episode.EpisodeFileId = episode.EpisodeFile.Value.Id;
}
}
Mocker.GetMock<IParsingService>()
.Setup(c => c.GetEpisodes(It.IsAny<string>(), It.IsAny<Series>()))
.Returns(new LocalEpisode
@ -102,6 +115,7 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
public void import_new_file_with_same_quality_should_succeed()
{
_fakeEpisode.EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.SDTV) };
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.SDTV));
var result = Subject.ImportFile(_fakeSeries, "file.ext");
@ -112,6 +126,7 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
public void import_new_file_with_better_quality_should_succeed()
{
_fakeEpisode.EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.SDTV) };
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.HDTV1080p));
var result = Subject.ImportFile(_fakeSeries, "file.ext");
@ -121,7 +136,8 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
[Test]
public void import_new_file_episode_has_better_quality_should_skip()
{
_fakeEpisode.EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV1080p) };
_fakeEpisode.EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV1080p), Id = 1 };
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.SDTV));
var result = Subject.ImportFile(_fakeSeries, "file.ext");
@ -158,7 +174,7 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
[Test]
public void import_file_with_no_episode_in_db_should_skip()
{
GivenEpisodes(new List<Episode>(), new QualityModel());
GivenEpisodes(new Episode[0], new QualityModel());
var result = Subject.ImportFile(_fakeSeries, "file.ext");
@ -185,8 +201,8 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
[Test]
public void skip_import_new_multi_part_file_episode_existing_has_better_quality()
{
_fakeEpisodes[0].EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV1080p) };
_fakeEpisodes[1].EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV1080p) };
_fakeEpisodes[0].EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV1080p), Id = 1 };
_fakeEpisodes[1].EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV1080p), Id = 1 };
GivenEpisodes(_fakeEpisodes, new QualityModel(Quality.SDTV));

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;

@ -0,0 +1,45 @@
using System;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
{
[TestFixture]
public class EpisodesRepositoryReadFixture : DbTest<EpisodeRepository, Episode>
{
private Series series;
[SetUp]
public void Setup()
{
series = Builder<Series>.CreateNew()
.With(s => s.Runtime = 30)
.BuildNew();
Db.Insert(series);
}
[Test]
public void should_get_episodes_by_file()
{
var episodeFile = Builder<EpisodeFile>.CreateNew().BuildNew();
Db.Insert(episodeFile);
var episode = Builder<Episode>.CreateListOfSize(2)
.All()
.With(e => e.SeriesId = series.Id)
.With(e => e.EpisodeFileId = episodeFile.Id)
.BuildListOfNew();
Db.InsertMany(episode);
var episodes = Subject.GetEpisodeByFileId(episodeFile.Id);
episodes.Should().HaveCount(2);
}
}
}

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using FizzWare.NBuilder;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore;

@ -59,8 +59,6 @@ namespace NzbDrone.Core.Datastore.Migration
.WithColumn("Ignored").AsBoolean().Nullable()
.WithColumn("EpisodeFileId").AsInt32().Nullable()
.WithColumn("AirDate").AsDateTime().Nullable()
.WithColumn("GrabDate").AsDateTime().Nullable()
.WithColumn("PostDownloadStatus").AsInt32().Nullable()
.WithColumn("AbsoluteEpisodeNumber").AsInt32().Nullable()
.WithColumn("SceneAbsoluteEpisodeNumber").AsInt32().Nullable()
.WithColumn("SceneSeasonNumber").AsInt32().Nullable()

@ -14,7 +14,7 @@ namespace NzbDrone.Core.Datastore
{
return relationshipBuilder.For(portalExpression.GetMemberName())
.LazyLoad((db, parent) => db.Query<TChild>()
.Single(c => c.Id == childIdSelector(parent)));
.SingleOrDefault(c => c.Id == childIdSelector(parent)));
}

@ -56,9 +56,12 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<Episode>().RegisterModel("Episodes")
.Ignore(e => e.SeriesTitle)
.Relationships.AutoMapICollectionOrComplexProperties();
.Relationship()
.HasOne(episode => episode.EpisodeFile, episode => episode.EpisodeFileId);
//.Relationships.AutoMapICollectionOrComplexProperties();
Mapper.Entity<EpisodeFile>().RegisterModel("EpisodeFiles");
Mapper.Entity<EpisodeFile>().RegisterModel("EpisodeFiles")
.Relationships.AutoMapICollectionOrComplexProperties();
Mapper.Entity<QualityProfile>().RegisterModel("QualityProfiles");

@ -26,7 +26,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public virtual bool IsSatisfiedBy(RemoteEpisode subject)
{
foreach (var file in subject.Episodes.Select(c => c.EpisodeFile).Where(c => c != null))
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
{
_logger.Trace("Comparing file quality with report. Existing file is {0}", file.Quality);

@ -69,8 +69,12 @@ namespace NzbDrone.Core.Jobs
public void HandleAsync(CommandExecutedEvent message)
{
var commandId = _scheduledTaskRepository.GetDefinition(message.Command.GetType()).Id;
_scheduledTaskRepository.SetLastExecutionTime(commandId, DateTime.UtcNow);
var scheduledTask = _scheduledTaskRepository.All().SingleOrDefault(c => c.TypeName == message.Command.GetType().FullName);
if (scheduledTask != null)
{
_scheduledTaskRepository.SetLastExecutionTime(scheduledTask.Id, DateTime.UtcNow);
}
}
}
}

@ -0,0 +1,14 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class CleanMediaFileDb : ICommand
{
public int SeriesId { get; private set; }
public CleanMediaFileDb(int seriesId)
{
SeriesId = seriesId;
}
}
}

@ -0,0 +1,17 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class DiskScanCommand : ICommand
{
public int? SeriesId { get; private set; }
public DiskScanCommand(int seriesId = 0)
{
if (seriesId != 0)
{
SeriesId = seriesId;
}
}
}
}

@ -4,40 +4,43 @@ using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Providers
namespace NzbDrone.Core.MediaFiles
{
public interface IDiskScanService
{
void Scan(Series series);
EpisodeFile ImportFile(Series series, string filePath);
string[] GetVideoFiles(string path, bool allDirectories = true);
}
public class DiskScanService : IDiskScanService
public class DiskScanService : IDiskScanService, IExecute<DiskScanCommand>
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static readonly string[] MediaExtensions = new[] { ".mkv", ".avi", ".wmv", ".mp4", ".mpg", ".mpeg", ".xvid", ".flv", ".mov", ".rm", ".rmvb", ".divx", ".dvr-ms", ".ts", ".ogm", ".m4v", ".strm" };
private readonly IDiskProvider _diskProvider;
private readonly ICleanGhostFiles _ghostFileCleaner;
private readonly ISeriesService _seriesService;
private readonly IMediaFileService _mediaFileService;
private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IParsingService _parsingService;
private readonly IMessageAggregator _messageAggregator;
public DiskScanService(IDiskProvider diskProvider, ICleanGhostFiles ghostFileCleaner, IMediaFileService mediaFileService, IVideoFileInfoReader videoFileInfoReader,
IParsingService parsingService)
public DiskScanService(IDiskProvider diskProvider, ISeriesService seriesService, IMediaFileService mediaFileService, IVideoFileInfoReader videoFileInfoReader,
IParsingService parsingService, IMessageAggregator messageAggregator)
{
_diskProvider = diskProvider;
_ghostFileCleaner = ghostFileCleaner;
_seriesService = seriesService;
_mediaFileService = mediaFileService;
_videoFileInfoReader = videoFileInfoReader;
_parsingService = parsingService;
_messageAggregator = messageAggregator;
}
public virtual void Scan(Series series)
private void Scan(Series series)
{
if (!_diskProvider.FolderExists(series.Path))
{
@ -45,7 +48,7 @@ namespace NzbDrone.Core.Providers
return;
}
_ghostFileCleaner.RemoveNonExistingFiles(series.Id);
_messageAggregator.PublishCommand(new CleanMediaFileDb(series.Id));
var mediaFileList = GetVideoFiles(series.Path);
@ -58,7 +61,7 @@ namespace NzbDrone.Core.Providers
//Todo: Move the episode linking to here, instead of import (or rename import)
}
public virtual EpisodeFile ImportFile(Series series, string filePath)
public EpisodeFile ImportFile(Series series, string filePath)
{
Logger.Trace("Importing file to database [{0}]", filePath);
@ -87,7 +90,7 @@ namespace NzbDrone.Core.Providers
}
}
if (parsedEpisode.Episodes.Any(e => e.EpisodeFile != null && e.EpisodeFile.Quality > parsedEpisode.Quality))
if (parsedEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && e.EpisodeFile.Value.Quality > parsedEpisode.Quality))
{
Logger.Trace("This file isn't an upgrade for all episodes. Skipping {0}", filePath);
return null;
@ -101,6 +104,7 @@ namespace NzbDrone.Core.Providers
episodeFile.Quality = parsedEpisode.Quality;
episodeFile.SeasonNumber = parsedEpisode.SeasonNumber;
episodeFile.SceneName = Path.GetFileNameWithoutExtension(filePath.CleanPath());
episodeFile.Episodes = parsedEpisode.Episodes;
//Todo: We shouldn't actually import the file until we confirm its the only one we want.
//Todo: Separate episodeFile creation from importing (pass file to import to import)
@ -108,7 +112,7 @@ namespace NzbDrone.Core.Providers
return episodeFile;
}
public virtual string[] GetVideoFiles(string path, bool allDirectories = true)
public string[] GetVideoFiles(string path, bool allDirectories = true)
{
Logger.Debug("Scanning '{0}' for video files", path);
@ -120,5 +124,24 @@ namespace NzbDrone.Core.Providers
Logger.Trace("{0} video files were found in {1}", mediaFileList.Count, path);
return mediaFileList.ToArray();
}
public void Execute(DiskScanCommand message)
{
var seriesToScan = new List<Series>();
if (message.SeriesId.HasValue)
{
seriesToScan.Add(_seriesService.GetSeries(message.SeriesId.Value));
}
else
{
seriesToScan.AddRange(_seriesService.GetAllSeries());
}
foreach (var series in seriesToScan)
{
Scan(series);
}
}
}
}

@ -6,20 +6,6 @@ namespace NzbDrone.Core.MediaFiles
{
public class EpisodeFile : ModelBase
{
public EpisodeFile()
{
}
public EpisodeFile(EpisodeFile source)
{
Id = source.Id;
SeriesId = source.SeriesId;
SeasonNumber = source.SeasonNumber;
Path = source.Path;
Size = source.Size;
}
public int SeriesId { get; set; }
public int SeasonNumber { get; set; }
public string Path { get; set; }

@ -1,48 +0,0 @@
using System;
using NLog;
using NzbDrone.Common;
namespace NzbDrone.Core.MediaFiles
{
public interface ICleanGhostFiles
{
void RemoveNonExistingFiles(int seriesId);
}
public class GhostFileCleanupService : ICleanGhostFiles
{
private readonly IMediaFileService _mediaFileService;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public GhostFileCleanupService(IMediaFileService mediaFileService, IDiskProvider diskProvider, Logger logger)
{
_mediaFileService = mediaFileService;
_diskProvider = diskProvider;
_logger = logger;
}
public void RemoveNonExistingFiles(int seriesId)
{
var seriesFile = _mediaFileService.GetFilesBySeries(seriesId);
foreach (var episodeFile in seriesFile)
{
try
{
if (!_diskProvider.FileExists(episodeFile.Path))
{
_logger.Trace("File [{0}] no longer exists on disk. removing from db", episodeFile.Path);
_mediaFileService.Delete(episodeFile);
}
}
catch (Exception ex)
{
var message = String.Format("Unable to cleanup EpisodeFile in DB: {0}", episodeFile.Id);
_logger.ErrorException(message, ex);
}
}
}
}
}

@ -0,0 +1,55 @@
using System;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles
{
public class MediaFileTableCleanupService : IExecute<CleanMediaFileDb>
{
private readonly IMediaFileService _mediaFileService;
private readonly IDiskProvider _diskProvider;
private readonly IEpisodeService _episodeService;
private readonly Logger _logger;
public MediaFileTableCleanupService(IMediaFileService mediaFileService, IDiskProvider diskProvider, IEpisodeService episodeService, Logger logger)
{
_mediaFileService = mediaFileService;
_diskProvider = diskProvider;
_episodeService = episodeService;
_logger = logger;
}
public void Execute(CleanMediaFileDb message)
{
var seriesFile = _mediaFileService.GetFilesBySeries(message.SeriesId);
foreach (var episodeFile in seriesFile)
{
try
{
if (!_diskProvider.FileExists(episodeFile.Path))
{
_logger.Trace("File [{0}] no longer exists on disk. removing from db", episodeFile.Path);
_mediaFileService.Delete(episodeFile);
}
if (!_episodeService.GetEpisodesByFileId(episodeFile.Id).Any())
{
_logger.Trace("File [{0}] is not assigned to any episodes. removing from db", episodeFile.Path);
_mediaFileService.Delete(episodeFile);
}
}
catch (Exception ex)
{
var errorMessage = String.Format("Unable to cleanup EpisodeFile in DB: {0}", episodeFile.Id);
_logger.ErrorException(errorMessage, ex);
}
}
}
}
}

@ -247,7 +247,9 @@
<Compile Include="Indexers\RssSyncCommand.cs" />
<Compile Include="Jobs\TaskManager.cs" />
<Compile Include="Lifecycle\ApplicationShutdownRequested.cs" />
<Compile Include="MediaFiles\Commands\CleanMediaFileDb.cs" />
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DiskScanCommand.cs" />
<Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" />
<Compile Include="Download\EpisodeGrabbedEvent.cs" />
<Compile Include="Download\SeriesRenamedEvent.cs" />
@ -296,7 +298,7 @@
<Compile Include="MediaFiles\EpisodeFileMovingService.cs" />
<Compile Include="MediaFiles\Events\EpisodeFileAddedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" />
<Compile Include="MediaFiles\GhostFileCleanupService.cs" />
<Compile Include="MediaFiles\MediaFileTableCleanupService.cs" />
<Compile Include="MediaFiles\MediaFileRepository.cs" />
<Compile Include="MetadataSource\IProvideEpisodeInfo.cs" />
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
@ -404,7 +406,7 @@
<Compile Include="DecisionEngine\Specifications\RetentionSpecification.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Providers\IDiskScanService.cs">
<Compile Include="MediaFiles\DiskScanService.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Download\Clients\BlackholeProvider.cs">

@ -40,7 +40,7 @@ namespace NzbDrone.Core.Providers.Converting
var outputFile = _configService.GetValue("iPodConvertDir", "");
var handBrakePreset = _configService.GetValue("HandBrakePreset", "iPhone & iPod Touch");
var handBrakeCommand = String.Format("-i \"{0}\" -o \"{1}\" --preset=\"{2}\"", episode.EpisodeFile.Path, outputFile, handBrakePreset);
var handBrakeCommand = String.Format("-i \"{0}\" -o \"{1}\" --preset=\"{2}\"", episode.EpisodeFile.Value.Path, outputFile, handBrakePreset);
var handBrakeFile = @"C:\Program Files (x86)\Handbrake\HandBrakeCLI.exe";
try

@ -30,7 +30,7 @@ namespace NzbDrone.Core.Providers
var misnamedFilesSelect = episodesWithFiles.AsParallel().Where(
w =>
w.First().EpisodeFile.Path !=
w.First().EpisodeFile.Value.Path !=
_buildFileNames.BuildFilename(w.Select(e => e).ToList(), w.First().Series, w.First().EpisodeFile)).Skip(Math.Max(pageSize * (pageNumber - 1), 0)).Take(pageSize);
//Process the episodes
@ -41,7 +41,7 @@ namespace NzbDrone.Core.Providers
var properName = _buildFileNames.BuildFilename(episodes, firstEpisode.Series,
firstEpisode.EpisodeFile);
var currentName = Path.GetFileNameWithoutExtension(firstEpisode.EpisodeFile.Path);
var currentName = Path.GetFileNameWithoutExtension(firstEpisode.EpisodeFile.Value.Path);
if (properName != currentName)
{

@ -1,4 +1,5 @@
using System;
using Marr.Data;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Model;
@ -18,38 +19,21 @@ namespace NzbDrone.Core.Tv
public string Overview { get; set; }
public Boolean Ignored { get; set; }
public PostDownloadStatusType PostDownloadStatus { get; set; }
public Nullable<Int32> AbsoluteEpisodeNumber { get; set; }
public int SceneSeasonNumber { get; set; }
public int SceneEpisodeNumber { get; set; }
public DateTime? GrabDate { get; set; }
public bool HasFile
{
get { return EpisodeFile != null; }
get { return EpisodeFileId != 0; }
}
public EpisodeStatuses Status
{
get
{
if (HasFile) return EpisodeStatuses.Ready;
if (GrabDate != null)
{
if (PostDownloadStatus == PostDownloadStatusType.Unpacking)
return EpisodeStatuses.Unpacking;
if (PostDownloadStatus == PostDownloadStatusType.Failed)
return EpisodeStatuses.Failed;
if (GrabDate.Value.AddDays(1) >= DateTime.Now)
return EpisodeStatuses.Downloading;
}
if (GrabDate != null && GrabDate.Value.AddDays(1) >= DateTime.Now)
return EpisodeStatuses.Downloading;
if (AirDate != null && AirDate.Value.Date == DateTime.Today)
return EpisodeStatuses.AirsToday;
@ -75,7 +59,7 @@ namespace NzbDrone.Core.Tv
public Series Series { get; set; }
public EpisodeFile EpisodeFile { get; set; }
public LazyLoaded<EpisodeFile> EpisodeFile { get; set; }
public override string ToString()
{

@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using FluentMigrator.Runner;
using Marr.Data;
using Marr.Data.QGen;
using NzbDrone.Common.Messaging;
@ -26,7 +22,6 @@ namespace NzbDrone.Core.Tv
List<Episode> EpisodesWithFiles();
List<Episode> EpisodesBetweenDates(DateTime startDate, DateTime endDate);
void SetIgnoreFlat(Episode episode, bool ignoreFlag);
void SetPostDownloadStatus(int episodeId, PostDownloadStatusType status);
void SetFileId(int episodeId, int fileId);
}
@ -62,7 +57,7 @@ namespace NzbDrone.Core.Tv
public List<Episode> GetEpisodeByFileId(int fileId)
{
return Query.Where(s => s.EpisodeFile != null && s.EpisodeFile.Id == fileId).ToList();
return Query.Where(e => e.EpisodeFileId == fileId).ToList();
}
public PagingSpec<Episode> EpisodesWithoutFiles(PagingSpec<Episode> pagingSpec, bool includeSpecials)
@ -74,7 +69,7 @@ namespace NzbDrone.Core.Tv
{
startingSeasonNumber = 0;
}
var pagingQuery = Query.Join<Episode, Series>(JoinType.Inner, e => e.Series, (e, s) => e.SeriesId == s.Id)
.Where(e => e.EpisodeFileId == 0)
.AndWhere(e => e.SeasonNumber >= startingSeasonNumber)
@ -98,7 +93,7 @@ namespace NzbDrone.Core.Tv
public List<Episode> EpisodesWithFiles()
{
return Query.Where(s => s.EpisodeFile != null).ToList();
return Query.Where(s => s.EpisodeFileId != 0).ToList();
}
public List<Episode> EpisodesBetweenDates(DateTime startDate, DateTime endDate)
@ -113,11 +108,6 @@ namespace NzbDrone.Core.Tv
SetFields(episode, p => p.Ignored);
}
public void SetPostDownloadStatus(int episodeId, PostDownloadStatusType status)
{
SetFields(new Episode { Id = episodeId, PostDownloadStatus = status }, episode => episode.PostDownloadStatus);
}
public void SetFileId(int episodeId, int fileId)
{
SetFields(new Episode { Id = episodeId, EpisodeFileId = fileId }, episode => episode.EpisodeFileId);

@ -29,16 +29,14 @@ namespace NzbDrone.Core.Tv
List<int> GetEpisodeNumbersBySeason(int seriesId, int seasonNumber);
void SetEpisodeIgnore(int episodeId, bool isIgnored);
bool IsFirstOrLastEpisodeOfSeason(int episodeId);
void SetPostDownloadStatus(List<int> episodeIds, PostDownloadStatusType postDownloadStatus);
void UpdateEpisodes(List<Episode> episodes);
List<Episode> EpisodesBetweenDates(DateTime start, DateTime end);
}
public class EpisodeService : IEpisodeService,
IHandle<EpisodeGrabbedEvent>,
IHandle<EpisodeFileDeletedEvent>,
IHandle<EpisodeFileAddedEvent>,
IHandleAsync<SeriesDeletedEvent>,
IHandleAsync<SeriesDeletedEvent>,
IHandleAsync<SeriesAddedEvent>
{
@ -166,7 +164,7 @@ namespace NzbDrone.Core.Tv
episodeToUpdate.EpisodeFileId > 0)
{
logger.Info("Unlinking episode file because TheTVDB changed the episode number...");
episodeToUpdate.EpisodeFile = null;
episodeToUpdate.EpisodeFileId = 0;
}
episodeToUpdate.SeriesId = series.Id;
@ -268,22 +266,6 @@ namespace NzbDrone.Core.Tv
return false;
}
public void SetPostDownloadStatus(List<int> episodeIds, PostDownloadStatusType postDownloadStatus)
{
if (episodeIds.Count == 0) throw new ArgumentException("episodeIds should contain one or more episode ids.");
foreach (var episodeId in episodeIds)
{
var episode = _episodeRepository.Get(episodeId);
episode.PostDownloadStatus = postDownloadStatus;
_episodeRepository.Update(episode);
}
logger.Trace("Updating PostDownloadStatus for {0} episode(s) to {1}", episodeIds.Count, postDownloadStatus);
}
public void UpdateEpisodes(List<Episode> episodes)
{
_episodeRepository.UpdateMany(episodes);
@ -296,16 +278,6 @@ namespace NzbDrone.Core.Tv
return LinkSeriesToEpisodes(episodes);
}
public void Handle(EpisodeGrabbedEvent message)
{
foreach (var episode in message.Episode.Episodes)
{
logger.Trace("Marking episode {0} as fetched.", episode.Id);
episode.GrabDate = DateTime.UtcNow;
_episodeRepository.Update(episode);
}
}
public void HandleAsync(SeriesDeletedEvent message)
{
var episodes = GetEpisodeBySeries(message.Series.Id);
@ -317,10 +289,8 @@ namespace NzbDrone.Core.Tv
foreach (var episode in GetEpisodesByFileId(message.EpisodeFile.Id))
{
_logger.Trace("Detaching episode {0} from file.", episode.Id);
episode.EpisodeFile = null;
episode.EpisodeFileId = 0;
episode.Ignored = _configService.AutoIgnorePreviouslyDownloadedEpisodes;
episode.GrabDate = null;
episode.PostDownloadStatus = PostDownloadStatusType.Unknown;
UpdateEpisode(episode);
}
}
@ -335,7 +305,6 @@ namespace NzbDrone.Core.Tv
foreach (var episode in message.EpisodeFile.Episodes.Value)
{
_episodeRepository.SetFileId(episode.Id, message.EpisodeFile.Id);
_episodeRepository.SetPostDownloadStatus(episode.Id, PostDownloadStatusType.NoError);
_logger.Debug("Linking [{0}] > [{1}]", message.EpisodeFile.Path, episode);
}
}

@ -25,7 +25,7 @@
layout="${date:format=yy-M-d HH\:mm\:ss.f}|${logger}}|${level}|${message}|${exception:format=ToString}"/>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="consoleLogger"/>
<logger name="*" minlevel="Trace" writeTo="consoleLogger"/>
<logger name="*" minlevel="Off" writeTo="udpTarget"/>
<logger name="*" minlevel="Warn" writeTo="rollingFileLogger"/>
</rules>

@ -156,7 +156,7 @@ define([
{
title : 'Update Library',
icon : 'icon-refresh',
command : 'updatelibrary',
command : 'diskscan',
successMessage: 'Library was updated!',
errorMessage : 'Library update failed!'
},

Loading…
Cancel
Save