Metadata cleanup and fixes

Fixed: Episode metadata will be renamed to match episode file names on refresh
Fixed: Episode metadata is renamed when episode file is renamed
pull/3113/head
Mark McDowall 10 years ago
parent e6e2f85d71
commit 7279b58a58

@ -1,4 +1,5 @@
using FluentValidation;
using System;
using FluentValidation;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Validation.Paths;
@ -11,7 +12,7 @@ namespace NzbDrone.Api.Config
{
SharedValidator.RuleFor(c => c.FileChmod).NotEmpty();
SharedValidator.RuleFor(c => c.FolderChmod).NotEmpty();
SharedValidator.RuleFor(c => c.RecycleBin).IsValidPath().SetValidator(pathExistsValidator);
SharedValidator.RuleFor(c => c.RecycleBin).IsValidPath().SetValidator(pathExistsValidator).When(c => !String.IsNullOrWhiteSpace(c.RecycleBin));
}
}
}

@ -1,10 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
@ -16,19 +11,12 @@ namespace NzbDrone.Core.Metadata.Consumers.Fake
{
public class FakeMetadata : MetadataBase<FakeMetadataSettings>
{
private readonly IDiskProvider _diskProvider;
private readonly IHttpProvider _httpProvider;
private readonly Logger _logger;
public FakeMetadata(IDiskProvider diskProvider, IHttpProvider httpProvider, Logger logger)
: base(diskProvider, httpProvider, logger)
{
_diskProvider = diskProvider;
_httpProvider = httpProvider;
_logger = logger;
}
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles)
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
{
throw new NotImplementedException();
}
@ -38,7 +26,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Fake
throw new NotImplementedException();
}
public override void AfterRename(Series series)
public override void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
{
throw new NotImplementedException();
}

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
@ -53,8 +54,10 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
private static readonly Regex SeasonImagesRegex = new Regex(@"^season(?<season>\d{2,}|-all|-specials)-(?<type>poster|banner|fanart)\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex EpisodeImageRegex = new Regex(@"-thumb\.(?:png|jpg)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles)
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
{
var metadataFiles = new List<MetadataFile>();
if (!_diskProvider.FolderExists(series.Path))
{
_logger.Info("Series folder does not exist, skipping metadata creation");
@ -63,26 +66,24 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
if (Settings.SeriesMetadata)
{
WriteTvShowNfo(series, existingMetadataFiles);
metadataFiles.Add(WriteTvShowNfo(series, existingMetadataFiles));
}
if (Settings.SeriesImages)
{
WriteSeriesImages(series, existingMetadataFiles);
metadataFiles.AddRange(WriteSeriesImages(series, existingMetadataFiles));
}
if (Settings.SeasonImages)
{
WriteSeasonImages(series, existingMetadataFiles);
metadataFiles.AddRange(WriteSeasonImages(series, existingMetadataFiles));
}
var episodeFiles = GetEpisodeFiles(series.Id);
foreach (var episodeFile in episodeFiles)
{
if (Settings.EpisodeMetadata)
{
WriteEpisodeNfo(series, episodeFile, existingMetadataFiles);
metadataFiles.Add(WriteEpisodeNfo(series, episodeFile, existingMetadataFiles));
}
}
@ -90,54 +91,83 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
{
if (Settings.EpisodeImages)
{
WriteEpisodeImages(series, episodeFile, existingMetadataFiles);
var metadataFile = WriteEpisodeImages(series, episodeFile, existingMetadataFiles);
if (metadataFile != null)
{
metadataFiles.Add(metadataFile);
}
}
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
}
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
{
var metadataFiles = new List<MetadataFile>();
if (Settings.EpisodeMetadata)
{
WriteEpisodeNfo(series, episodeFile, new List<MetadataFile>());
metadataFiles.Add(WriteEpisodeNfo(series, episodeFile, new List<MetadataFile>()));
}
if (Settings.EpisodeImages)
{
var metadataFile = WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
if (metadataFile != null)
{
metadataFiles.Add(metadataFile);
}
WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(metadataFiles));
}
public override void AfterRename(Series series)
public override void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles)
{
//TODO: This should be part of the base class, but could be overwritten if the logic needs to be different
//or it could be done in MetadataService instead of having each metadata consumer do it
var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
var episodeFilesMetadata = _metadataFileService.GetFilesBySeries(series.Id).Where(c => c.EpisodeFileId > 0).ToList();
var episodeFilesMetadata = existingMetadataFiles.Where(c => c.EpisodeFileId > 0).ToList();
var updatedMetadataFiles = new List<MetadataFile>();
foreach (var episodeFile in episodeFiles)
{
var metadataFiles = episodeFilesMetadata.Where(m => m.EpisodeFileId == episodeFile.Id).ToList();
var episodeFilenameWithoutExtension =
Path.GetFileNameWithoutExtension(DiskProviderBase.GetRelativePath(series.Path, episodeFile.Path));
foreach (var metadataFile in metadataFiles)
{
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(metadataFile.RelativePath);
var extension = Path.GetExtension(metadataFile.RelativePath);
string newFilename;
if (!fileNameWithoutExtension.Equals(episodeFilenameWithoutExtension))
if (metadataFile.Type == MetadataType.EpisodeImage)
{
var source = Path.Combine(series.Path, metadataFile.RelativePath);
var destination = Path.Combine(series.Path, fileNameWithoutExtension + extension);
newFilename = GetEpisodeImageFilename(episodeFile.Path);
}
_diskProvider.MoveFile(source, destination);
metadataFile.RelativePath = fileNameWithoutExtension + extension;
else if (metadataFile.Type == MetadataType.EpisodeMetadata)
{
newFilename = GetEpisodeNfoFilename(episodeFile.Path);
}
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadataFile));
else
{
_logger.Trace("Unknown episode file metadata: {0}", metadataFile.RelativePath);
continue;
}
var existingFilename = Path.Combine(series.Path, metadataFile.RelativePath);
if (!newFilename.PathEquals(existingFilename))
{
_diskProvider.MoveFile(existingFilename, newFilename);
metadataFile.RelativePath = DiskProviderBase.GetRelativePath(series.Path, newFilename);
updatedMetadataFiles.Add(metadataFile);
}
}
}
_eventAggregator.PublishEvent(new MetadataFilesUpdated(updatedMetadataFiles));
}
public override MetadataFile FindMetadataFile(Series series, string path)
@ -205,7 +235,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
return null;
}
private void WriteTvShowNfo(Series series, List<MetadataFile> existingMetadataFiles)
private MetadataFile WriteTvShowNfo(Series series, List<MetadataFile> existingMetadataFiles)
{
_logger.Trace("Generating tvshow.nfo for: {0}", series.Title);
var sb = new StringBuilder();
@ -266,11 +296,11 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
};
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
return metadata;
}
}
private void WriteSeriesImages(Series series, List<MetadataFile> existingMetadataFiles)
private IEnumerable<MetadataFile> WriteSeriesImages(Series series, List<MetadataFile> existingMetadataFiles)
{
foreach (var image in series.Images)
{
@ -295,11 +325,11 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
RelativePath = DiskProviderBase.GetRelativePath(series.Path, destination)
};
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
yield return metadata;
}
}
private void WriteSeasonImages(Series series, List<MetadataFile> existingMetadataFiles)
private IEnumerable<MetadataFile> WriteSeasonImages(Series series, List<MetadataFile> existingMetadataFiles)
{
foreach (var season in series.Seasons)
{
@ -327,14 +357,28 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
RelativePath = DiskProviderBase.GetRelativePath(series.Path, path)
};
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
yield return metadata;
}
}
}
private void WriteEpisodeNfo(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
private MetadataFile WriteEpisodeNfo(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
{
var filename = episodeFile.Path.Replace(Path.GetExtension(episodeFile.Path), ".nfo");
var filename = GetEpisodeNfoFilename(episodeFile.Path);
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
c.EpisodeFileId == episodeFile.Id);
if (existingMetadata != null)
{
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!filename.PathEquals(fullPath))
{
_diskProvider.MoveFile(fullPath, filename);
existingMetadata.RelativePath = relativePath;
}
}
_logger.Debug("Generating {0} for: {1}", filename, episodeFile.Path);
@ -390,8 +434,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
_logger.Debug("Saving episodedetails to: {0}", filename);
_diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
c.EpisodeFileId == episodeFile.Id) ??
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
@ -401,19 +444,38 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
};
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
return metadata;
}
private void WriteEpisodeImages(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
private MetadataFile WriteEpisodeImages(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
{
var screenshot = episodeFile.Episodes.Value.First().Images.Single(i => i.CoverType == MediaCoverTypes.Screenshot);
var screenshot = episodeFile.Episodes.Value.First().Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
if (screenshot == null)
{
_logger.Trace("Episode screenshot not available");
return null;
}
var filename = GetEpisodeImageFilename(episodeFile.Path);
var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename);
var filename = Path.ChangeExtension(episodeFile.Path, "").Trim('.') + "-thumb.jpg";
var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage &&
c.EpisodeFileId == episodeFile.Id);
if (existingMetadata != null)
{
var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath);
if (!filename.PathEquals(fullPath))
{
_diskProvider.MoveFile(fullPath, filename);
existingMetadata.RelativePath = relativePath;
}
}
DownloadImage(series, screenshot.Url, filename);
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage &&
c.EpisodeFileId == episodeFile.Id) ??
var metadata = existingMetadata ??
new MetadataFile
{
SeriesId = series.Id,
@ -423,21 +485,17 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
};
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
return metadata;
}
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
private string GetEpisodeNfoFilename(string episodeFilePath)
{
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
foreach (var episodeFile in episodeFiles)
{
var localEpisodeFile = episodeFile;
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
}
return Path.ChangeExtension(episodeFilePath, "nfo");
}
return episodeFiles;
private string GetEpisodeImageFilename(string episodeFilePath)
{
return Path.ChangeExtension(episodeFilePath, "").Trim('.') + "-thumb.jpg";
}
}
}

@ -0,0 +1,15 @@
using System.Collections.Generic;
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Metadata.Files
{
public class MetadataFilesUpdated : IEvent
{
public List<MetadataFile> MetadataFiles { get; set; }
public MetadataFilesUpdated(List<MetadataFile> metadataFiles)
{
MetadataFiles = metadataFiles;
}
}
}

@ -8,9 +8,9 @@ namespace NzbDrone.Core.Metadata
{
public interface IMetadata : IProvider
{
void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles);
void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload);
void AfterRename(Series series);
void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
MetadataFile FindMetadataFile(Series series, string path);
}
}

@ -1,9 +1,13 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Metadata.Files;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Metadata
{
@ -15,16 +19,22 @@ namespace NzbDrone.Core.Metadata
private readonly IMetadataFactory _metadataFactory;
private readonly IMetadataFileService _metadataFileService;
private readonly ICleanMetadataService _cleanMetadataService;
private readonly IMediaFileService _mediaFileService;
private readonly IEpisodeService _episodeService;
private readonly Logger _logger;
public MetadataService(IMetadataFactory metadataFactory,
IMetadataFileService metadataFileService,
ICleanMetadataService cleanMetadataService,
IMediaFileService mediaFileService,
IEpisodeService episodeService,
Logger logger)
{
_metadataFactory = metadataFactory;
_metadataFileService = metadataFileService;
_cleanMetadataService = cleanMetadataService;
_mediaFileService = mediaFileService;
_episodeService = episodeService;
_logger = logger;
}
@ -35,7 +45,7 @@ namespace NzbDrone.Core.Metadata
foreach (var consumer in _metadataFactory.Enabled())
{
consumer.OnSeriesUpdated(message.Series, seriesMetadata.Where(c => c.Consumer == consumer.GetType().Name).ToList());
consumer.OnSeriesUpdated(message.Series, GetMetadataFilesForConsumer(consumer, seriesMetadata), GetEpisodeFiles(message.Series.Id));
}
}
@ -49,10 +59,31 @@ namespace NzbDrone.Core.Metadata
public void Handle(SeriesRenamedEvent message)
{
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
foreach (var consumer in _metadataFactory.Enabled())
{
consumer.AfterRename(message.Series);
consumer.AfterRename(message.Series, GetMetadataFilesForConsumer(consumer, seriesMetadata), GetEpisodeFiles(message.Series.Id));
}
}
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
{
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
foreach (var episodeFile in episodeFiles)
{
var localEpisodeFile = episodeFile;
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
}
return episodeFiles;
}
private List<MetadataFile> GetMetadataFilesForConsumer(IMetadata consumer, List<MetadataFile> seriesMetadata)
{
return seriesMetadata.Where(c => c.Consumer == consumer.GetType().Name).ToList();
}
}
}

@ -12,7 +12,7 @@ using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.Metadata
{
public class ExistingMetadataService : IHandleAsync<SeriesUpdatedEvent>
public class ExistingMetadataService : IHandle<SeriesUpdatedEvent>
{
private readonly IDiskProvider _diskProvider;
private readonly IMetadataFileService _metadataFileService;
@ -33,7 +33,7 @@ namespace NzbDrone.Core.Metadata
_consumers = consumers.ToList();
}
public void HandleAsync(SeriesUpdatedEvent message)
public void Handle(SeriesUpdatedEvent message)
{
if (!_diskProvider.FolderExists(message.Series.Path)) return;
@ -42,7 +42,9 @@ namespace NzbDrone.Core.Metadata
var filesOnDisk = _diskProvider.GetFiles(message.Series.Path, SearchOption.AllDirectories);
var possibleMetadataFiles = filesOnDisk.Where(c => !MediaFileExtensions.Extensions.Contains(Path.GetExtension(c).ToLower())).ToList();
var filteredFiles = _metadataFileService.FilterExistingFiles(possibleMetadataFiles, message.Series);
var metadataFiles = new List<MetadataFile>();
foreach (var possibleMetadataFile in filteredFiles)
{
foreach (var consumer in _consumers)
@ -71,9 +73,11 @@ namespace NzbDrone.Core.Metadata
metadata.EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId;
}
_metadataFileService.Upsert(metadata);
metadataFiles.Add(metadata);
}
}
_metadataFileService.Upsert(metadataFiles);
}
}
}

@ -18,14 +18,14 @@ namespace NzbDrone.Core.Metadata.Files
List<MetadataFile> GetFilesByEpisodeFile(int episodeFileId);
MetadataFile FindByPath(string path);
List<string> FilterExistingFiles(List<string> files, Series series);
MetadataFile Upsert(MetadataFile metadataFile);
void Upsert(List<MetadataFile> metadataFiles);
void Delete(int id);
}
public class MetadataFileService : IMetadataFileService,
IHandleAsync<SeriesDeletedEvent>,
IHandleAsync<EpisodeFileDeletedEvent>,
IHandle<MetadataFileUpdated>
IHandle<MetadataFilesUpdated>
{
private readonly IMetadataFileRepository _repository;
private readonly ISeriesService _seriesService;
@ -67,10 +67,12 @@ namespace NzbDrone.Core.Metadata.Files
return files.Except(seriesFiles, PathEqualityComparer.Instance).ToList();
}
public MetadataFile Upsert(MetadataFile metadataFile)
public void Upsert(List<MetadataFile> metadataFiles)
{
metadataFile.LastUpdated = DateTime.UtcNow;
return _repository.Upsert(metadataFile);
metadataFiles.ForEach(m => m.LastUpdated = DateTime.UtcNow);
_repository.InsertMany(metadataFiles.Where(m => m.Id == 0).ToList());
_repository.UpdateMany(metadataFiles.Where(m => m.Id > 0).ToList());
}
public void Delete(int id)
@ -103,9 +105,9 @@ namespace NzbDrone.Core.Metadata.Files
_repository.DeleteForEpisodeFile(episodeFile.Id);
}
public void Handle(MetadataFileUpdated message)
public void Handle(MetadataFilesUpdated message)
{
Upsert(message.Metadata);
Upsert(message.MetadataFiles);
}
}
}

@ -1,14 +0,0 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Metadata.Files
{
public class MetadataFileUpdated : IEvent
{
public MetadataFile Metadata { get; set; }
public MetadataFileUpdated(MetadataFile metadata)
{
Metadata = metadata;
}
}
}

@ -42,9 +42,9 @@ namespace NzbDrone.Core.Metadata
public ProviderDefinition Definition { get; set; }
public abstract void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles);
public abstract void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
public abstract void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload);
public abstract void AfterRename(Series series);
public abstract void AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
public abstract MetadataFile FindMetadataFile(Series series, string path);
protected TSettings Settings

@ -344,10 +344,10 @@
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadata.cs" />
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadataSettings.cs" />
<Compile Include="Metadata\ExistingMetadataService.cs" />
<Compile Include="Metadata\Files\MetadataFilesUpdated.cs" />
<Compile Include="Metadata\Files\MetadataFile.cs" />
<Compile Include="Metadata\Files\MetadataFileRepository.cs" />
<Compile Include="Metadata\Files\MetadataFileService.cs" />
<Compile Include="Metadata\Files\MetadataFileUpdated.cs" />
<Compile Include="Metadata\IMetadata.cs" />
<Compile Include="Metadata\MetadataBase.cs" />
<Compile Include="MetadataSource\Trakt\TraktException.cs" />

Loading…
Cancel
Save